Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ protected override void ProcessRecord()
bool objServiceShouldBeDisposed = false;
try
{
if (_ParameterSetName.Equals("InputObject", StringComparison.OrdinalIgnoreCase) && InputObject != null)
if (InputObject != null)
{
service = InputObject;
Name = service.ServiceName;
Expand Down Expand Up @@ -2167,6 +2167,195 @@ protected override void BeginProcessing()
} // 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, perhaps just an assert?

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
Expand Down Expand Up @@ -2264,8 +2453,10 @@ internal static class NativeMethods
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;
Expand Down Expand Up @@ -2297,6 +2488,12 @@ bool CloseServiceHandle(
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ CmdletsToExport=@("Add-Content",
"Restart-Service",
"Set-Service",
"New-Service",
"Remove-Service",
"Set-Content",
"Set-ItemProperty",
"Test-Connection",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ CmdletsToExport=@("Add-Content",
"Restart-Service",
"Set-Service",
"New-Service",
"Remove-Service",
"Set-Content",
"Set-ItemProperty",
"Set-WmiInstance",
Expand Down
1 change: 1 addition & 0 deletions src/System.Management.Automation/utils/PInvokeDllNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,6 @@ internal static class PinvokeDllNames
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*/
}
}
Original file line number Diff line number Diff line change
@@ -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 ) {
Expand Down Expand Up @@ -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
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add tests which validates the error cases?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add tests for pipeline input.


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",
Expand Down
1 change: 1 addition & 0 deletions test/powershell/engine/Basic/DefaultCommands.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down