[Feature]Added Remove-service to Management module (#4858)

* [Feature]Remove-Service added to Management module

* [Feature]Capitalization

* [Feature]Added test cases for Remove-Service, Removed return value

* [Feature]Documentation from copy-paste code corrected to reflect the new function

* [Feature]Erroraction to be sure that an error is thrown in testcase

* [Feature]Removed direct reference to module in Pester test

* [Feature]Removed extra line in test

* [Feature]Use FullyQualifiedErrorId in Remove-Service test without a valid servicename

* [Feature]Exposed Remove-Service

* [Feature]Consistent casing, Named Arguments & Remove incorrect exception on cleanup

* [Feature]Remove-Service test should fail if the service was not removed

* [Feature]Cleanup comments & add Remove-Service for CI

* [Feature]Remove-Service in CI set in alphabetic order

* [Feature]Use ParameterSetName instead of _ParameterSetName and rewrite test which used the function class directly

* Revert "[Feature]Use ParameterSetName instead of _ParameterSetName and rewrite test which used the function class directly"

This reverts commit da41f7deb8.

* [Feature]Remove _ParameterSetName check & added test for pipeline input in
Set-Service
This commit is contained in:
Jonas Andersen
2017-09-20 17:24:39 -07:00
committed by Aditya Patwardhan
parent 4c6ad4a133
commit f281671375
7 changed files with 265 additions and 2 deletions
@@ -1601,7 +1601,7 @@ namespace Microsoft.PowerShell.Commands
bool objServiceShouldBeDisposed = false;
try
{
if (_ParameterSetName.Equals("InputObject", StringComparison.OrdinalIgnoreCase) && InputObject != null)
if (InputObject != null)
{
service = InputObject;
Name = service.ServiceName;
@@ -2167,6 +2167,195 @@ namespace Microsoft.PowerShell.Commands
} // class NewServiceCommand
#endregion NewServiceCommand
#region RemoveServiceCommand
/// <summary>
/// This class implements the Remove-Service command
/// </summary>
[Cmdlet(VerbsCommon.Remove, "Service", SupportsShouldProcess = true, DefaultParameterSetName = "Name")]
public class RemoveServiceCommand : ServiceBaseCommand
{
#region Parameters
/// <summary>
/// Name of the service to remove
/// </summary>
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Name")]
[Alias("ServiceName", "SN")]
public string Name { get; set; }
/// <summary>
/// The following is the definition of the input parameter "InputObject".
/// Specifies ServiceController object representing the services to be removed.
/// Enter a variable that contains the objects or type a command or expression
/// that gets the objects.
/// </summary>
[Parameter(ValueFromPipeline = true, ParameterSetName = "InputObject")]
public ServiceController InputObject { get; set; }
/// <summary>
/// The following is the definition of the input parameter "ComputerName".
/// Set the properties of service running on the list of computer names
/// specified. The default is the local computer.
/// Type the NETBIOS name, an IP address, or a fully-qualified domain name of
/// one or more remote computers. To indicate the local computer, use the
/// computer name, "localhost" or a dot (.). When the computer is in a different
/// domain than the user, the fully-qualified domain name is required.
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
[Alias("cn")]
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public String[] ComputerName { get; set; } = new string[] { "." };
#endregion Parameters
#region Overrides
/// <summary>
/// Remove the service
/// </summary>
[ArchitectureSensitive]
protected override void ProcessRecord()
{
ServiceController service = null;
string serviceComputerName = null;
foreach (string computer in ComputerName)
{
bool objServiceShouldBeDisposed = false;
try
{
if (InputObject != null)
{
service = InputObject;
Name = service.ServiceName;
serviceComputerName = service.MachineName;
objServiceShouldBeDisposed = false;
}
else
{
serviceComputerName = computer;
// "new ServiceController" will succeed even if there is no such service.
// This checks whether the service actually exists.
service = new ServiceController(Name, serviceComputerName);
objServiceShouldBeDisposed = true;
}
Diagnostics.Assert(!String.IsNullOrEmpty(Name), "null ServiceName");
string unusedByDesign = service.DisplayName;
}
catch (ArgumentException ex)
{
// Cannot use WriteNonterminatingError as service is null
ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.ObjectNotFound, computer);
WriteError(er);
continue;
}
catch (InvalidOperationException ex)
{
// Cannot use WriteNonterminatingError as service is null
ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.ObjectNotFound, computer);
WriteError(er);
continue;
}
try // In finally we ensure dispose, if object not pipelined.
{
// Confirm the operation first.
// This is always false if WhatIf is set.
if (!ShouldProcessServiceOperation(service))
{
continue;
}
NakedWin32Handle hScManager = IntPtr.Zero;
NakedWin32Handle hService = IntPtr.Zero;
try
{
hScManager = NativeMethods.OpenSCManagerW(
lpMachineName: serviceComputerName,
lpDatabaseName: null,
dwDesiredAccess: NativeMethods.SC_MANAGER_ALL_ACCESS
);
if (IntPtr.Zero == hScManager)
{
int lastError = Marshal.GetLastWin32Error();
Win32Exception exception = new Win32Exception(lastError);
WriteObject(exception);
WriteNonTerminatingError(
service,
serviceComputerName,
exception,
"ComputerAccessDenied",
ServiceResources.ComputerAccessDenied,
ErrorCategory.PermissionDenied);
continue;
}
hService = NativeMethods.OpenServiceW(
hScManager,
Name,
NativeMethods.SERVICE_DELETE
);
if (IntPtr.Zero == hService)
{
int lastError = Marshal.GetLastWin32Error();
Win32Exception exception = new Win32Exception(lastError);
WriteNonTerminatingError(
service,
exception,
"CouldNotRemoveService",
ServiceResources.CouldNotSetService,
ErrorCategory.PermissionDenied);
continue;
}
bool status = NativeMethods.DeleteService(hService);
if (!status)
{
int lastError = Marshal.GetLastWin32Error();
Win32Exception exception = new Win32Exception(lastError);
WriteNonTerminatingError(
service,
exception,
"CouldNotRemoveService",
ServiceResources.CouldNotRemoveService,
ErrorCategory.PermissionDenied);
}
}
finally
{
if (IntPtr.Zero != hService)
{
bool succeeded = NativeMethods.CloseServiceHandle(hService);
if (!succeeded)
{
int lastError = Marshal.GetLastWin32Error();
Diagnostics.Assert(lastError != 0, "ErrorCode not success");
}
}
if (IntPtr.Zero != hScManager)
{
bool succeeded = NativeMethods.CloseServiceHandle(hScManager);
if (!succeeded)
{
int lastError = Marshal.GetLastWin32Error();
Diagnostics.Assert(lastError != 0, "ErrorCode not success");
}
}
} // Finally
} // End try
finally
{
if (objServiceShouldBeDisposed)
{
service.Dispose();
}
}
}// End for
}
#endregion Overrides
} // class RemoveServiceCommand
#endregion RemoveServiceCommand
#region ServiceCommandException
/// <summary>
/// Non-terminating errors occurring in the service noun commands
@@ -2264,8 +2453,10 @@ namespace Microsoft.PowerShell.Commands
internal const int ERROR_SERVICE_NOT_ACTIVE = 1062;
internal const DWORD SC_MANAGER_CONNECT = 1;
internal const DWORD SC_MANAGER_CREATE_SERVICE = 2;
internal const DWORD SC_MANAGER_ALL_ACCESS = 0xf003f;
internal const DWORD SERVICE_QUERY_CONFIG = 1;
internal const DWORD SERVICE_CHANGE_CONFIG = 2;
internal const DWORD SERVICE_DELETE = 0x10000;
internal const DWORD SERVICE_NO_CHANGE = 0xffffffff;
internal const DWORD SERVICE_AUTO_START = 0x2;
internal const DWORD SERVICE_DEMAND_START = 0x3;
@@ -2297,6 +2488,12 @@ namespace Microsoft.PowerShell.Commands
NakedWin32Handle hSCManagerOrService
);
[DllImport(PinvokeDllNames.DeleteServiceDllName, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern
bool DeleteService(
NakedWin32Handle hService
);
[DllImport(PinvokeDllNames.ChangeServiceConfigWDllName, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern
bool ChangeServiceConfigW(
@@ -171,6 +171,9 @@
<data name="CouldNotNewServiceDescription" xml:space="preserve">
<value>Service '{1} ({0})' was created, but its description cannot be configured due to the following error: {2}</value>
</data>
<data name="CouldNotRemoveService" xml:space="preserve">
<value>Service '{1} ({0})' cannot be removed due to the following error: {2}</value>
</data>
<data name="CouldNotAccessDependentServices" xml:space="preserve">
<value>'Cannot access dependent services of '{1} ({0})'</value>
</data>
@@ -56,6 +56,7 @@ CmdletsToExport=@("Add-Content",
"Restart-Service",
"Set-Service",
"New-Service",
"Remove-Service",
"Set-Content",
"Set-ItemProperty",
"Test-Connection",
@@ -68,6 +68,7 @@ CmdletsToExport=@("Add-Content",
"Restart-Service",
"Set-Service",
"New-Service",
"Remove-Service",
"Set-Content",
"Set-ItemProperty",
"Set-WmiInstance",
@@ -135,5 +135,6 @@ namespace System.Management.Automation
internal const string Process32FirstDllName = "api-ms-win-core-toolhelp-l1-1-0"; /*121*/
internal const string Process32NextDllName = "api-ms-win-core-toolhelp-l1-1-0"; /*122*/
internal const string GetACPDllName = "api-ms-win-core-localization-l1-2-0.dll"; /*123*/
internal const string DeleteServiceDllName = "api-ms-win-service-management-l1-1-0.dll"; /*124*/
}
}
@@ -1,4 +1,4 @@
Describe "Set/New-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows" {
Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows" {
BeforeAll {
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
if ( -not $IsWindows ) {
@@ -195,6 +195,65 @@ Describe "Set/New-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows"
}
}
It "Remove-Service can remove a service" {
try {
$servicename = "testremoveservice"
$parameters = @{
Name = $servicename;
BinaryPathName = "$PSHOME\powershell.exe"
}
$service = New-Service @parameters
$service | Should Not BeNullOrEmpty
Remove-Service -Name $servicename
$service = Get-Service -Name $servicename -ErrorAction SilentlyContinue
$service | Should BeNullOrEmpty
}
finally {
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
}
}
It "Remove-Service can accept pipeline input of a ServiceController" {
try {
$servicename = "testremoveservice"
$parameters = @{
Name = $servicename;
BinaryPathName = "$PSHOME\powershell.exe"
}
$service = New-Service @parameters
$service | Should Not BeNullOrEmpty
Get-Service -Name $servicename | Remove-Service
$service = Get-Service -Name $servicename -ErrorAction SilentlyContinue
$service | Should BeNullOrEmpty
}
finally {
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
}
}
It "Remove-Service cannot accept a service that does not exist" {
{ Remove-Service -Name "testremoveservice" -ErrorAction 'Stop' } | ShouldBeErrorId "InvalidOperationException,Microsoft.PowerShell.Commands.RemoveServiceCommand"
}
It "Set-Service can accept pipeline input of a ServiceController" {
try {
$servicename = "testsetservice"
$newdisplayname = "newdisplayname"
$parameters = @{
Name = $servicename;
BinaryPathName = "$PSHOME\powershell.exe"
}
$service = New-Service @parameters
$service | Should Not BeNullOrEmpty
Get-Service -Name $servicename | Set-Service -DisplayName $newdisplayname
$service = Get-Service -Name $servicename
$service.DisplayName | Should BeExactly $newdisplayname
}
finally {
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
}
}
It "Using bad parameters will fail for '<name>' where '<parameter>' = '<value>'" -TestCases @(
@{cmdlet="New-Service"; name = 'credtest' ; parameter = "Credential" ; value = (
[System.Management.Automation.PSCredential]::new("username",
@@ -374,6 +374,7 @@ Describe "Verify approved aliases list" -Tags "CI" {
"Cmdlet", "Remove-PSDrive", , $($FullCLR -or $CoreWindows -or $CoreUnix)
"Cmdlet", "Remove-PSSession", , $($FullCLR -or $CoreWindows -or $CoreUnix)
"Cmdlet", "Remove-PSSnapin", , $($FullCLR )
"Cmdlet", "Remove-Service", , $($FullCLR -or $CoreWindows )
"Cmdlet", "Remove-TypeData", , $($FullCLR -or $CoreWindows -or $CoreUnix)
"Cmdlet", "Remove-Variable", , $($FullCLR -or $CoreWindows -or $CoreUnix)
"Cmdlet", "Remove-WmiObject", , $($FullCLR )