Skip to content

Commit f281671

Browse files
joandrsnadityapatwardhan
authored andcommitted
[Feature]Added Remove-service to Management module (PowerShell#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 da41f7d. * [Feature]Remove _ParameterSetName check & added test for pipeline input in Set-Service
1 parent 4c6ad4a commit f281671

File tree

7 files changed

+265
-2
lines changed

7 files changed

+265
-2
lines changed

src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1601,7 +1601,7 @@ protected override void ProcessRecord()
16011601
bool objServiceShouldBeDisposed = false;
16021602
try
16031603
{
1604-
if (_ParameterSetName.Equals("InputObject", StringComparison.OrdinalIgnoreCase) && InputObject != null)
1604+
if (InputObject != null)
16051605
{
16061606
service = InputObject;
16071607
Name = service.ServiceName;
@@ -2167,6 +2167,195 @@ protected override void BeginProcessing()
21672167
} // class NewServiceCommand
21682168
#endregion NewServiceCommand
21692169

2170+
#region RemoveServiceCommand
2171+
/// <summary>
2172+
/// This class implements the Remove-Service command
2173+
/// </summary>
2174+
[Cmdlet(VerbsCommon.Remove, "Service", SupportsShouldProcess = true, DefaultParameterSetName = "Name")]
2175+
public class RemoveServiceCommand : ServiceBaseCommand
2176+
{
2177+
#region Parameters
2178+
2179+
/// <summary>
2180+
/// Name of the service to remove
2181+
/// </summary>
2182+
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "Name")]
2183+
[Alias("ServiceName", "SN")]
2184+
public string Name { get; set; }
2185+
2186+
/// <summary>
2187+
/// The following is the definition of the input parameter "InputObject".
2188+
/// Specifies ServiceController object representing the services to be removed.
2189+
/// Enter a variable that contains the objects or type a command or expression
2190+
/// that gets the objects.
2191+
/// </summary>
2192+
[Parameter(ValueFromPipeline = true, ParameterSetName = "InputObject")]
2193+
public ServiceController InputObject { get; set; }
2194+
2195+
/// <summary>
2196+
/// The following is the definition of the input parameter "ComputerName".
2197+
/// Set the properties of service running on the list of computer names
2198+
/// specified. The default is the local computer.
2199+
/// Type the NETBIOS name, an IP address, or a fully-qualified domain name of
2200+
/// one or more remote computers. To indicate the local computer, use the
2201+
/// computer name, "localhost" or a dot (.). When the computer is in a different
2202+
/// domain than the user, the fully-qualified domain name is required.
2203+
/// </summary>
2204+
[Parameter(ValueFromPipelineByPropertyName = true)]
2205+
[ValidateNotNullOrEmpty]
2206+
[Alias("cn")]
2207+
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
2208+
public String[] ComputerName { get; set; } = new string[] { "." };
2209+
2210+
#endregion Parameters
2211+
2212+
#region Overrides
2213+
/// <summary>
2214+
/// Remove the service
2215+
/// </summary>
2216+
[ArchitectureSensitive]
2217+
protected override void ProcessRecord()
2218+
{
2219+
ServiceController service = null;
2220+
string serviceComputerName = null;
2221+
foreach (string computer in ComputerName)
2222+
{
2223+
bool objServiceShouldBeDisposed = false;
2224+
try
2225+
{
2226+
if (InputObject != null)
2227+
{
2228+
service = InputObject;
2229+
Name = service.ServiceName;
2230+
serviceComputerName = service.MachineName;
2231+
objServiceShouldBeDisposed = false;
2232+
}
2233+
else
2234+
{
2235+
serviceComputerName = computer;
2236+
// "new ServiceController" will succeed even if there is no such service.
2237+
// This checks whether the service actually exists.
2238+
service = new ServiceController(Name, serviceComputerName);
2239+
objServiceShouldBeDisposed = true;
2240+
}
2241+
Diagnostics.Assert(!String.IsNullOrEmpty(Name), "null ServiceName");
2242+
string unusedByDesign = service.DisplayName;
2243+
}
2244+
catch (ArgumentException ex)
2245+
{
2246+
// Cannot use WriteNonterminatingError as service is null
2247+
ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.ObjectNotFound, computer);
2248+
WriteError(er);
2249+
continue;
2250+
}
2251+
catch (InvalidOperationException ex)
2252+
{
2253+
// Cannot use WriteNonterminatingError as service is null
2254+
ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.ObjectNotFound, computer);
2255+
WriteError(er);
2256+
continue;
2257+
}
2258+
2259+
try // In finally we ensure dispose, if object not pipelined.
2260+
{
2261+
// Confirm the operation first.
2262+
// This is always false if WhatIf is set.
2263+
if (!ShouldProcessServiceOperation(service))
2264+
{
2265+
continue;
2266+
}
2267+
2268+
NakedWin32Handle hScManager = IntPtr.Zero;
2269+
NakedWin32Handle hService = IntPtr.Zero;
2270+
try
2271+
{
2272+
hScManager = NativeMethods.OpenSCManagerW(
2273+
lpMachineName: serviceComputerName,
2274+
lpDatabaseName: null,
2275+
dwDesiredAccess: NativeMethods.SC_MANAGER_ALL_ACCESS
2276+
);
2277+
if (IntPtr.Zero == hScManager)
2278+
{
2279+
int lastError = Marshal.GetLastWin32Error();
2280+
Win32Exception exception = new Win32Exception(lastError);
2281+
WriteObject(exception);
2282+
WriteNonTerminatingError(
2283+
service,
2284+
serviceComputerName,
2285+
exception,
2286+
"ComputerAccessDenied",
2287+
ServiceResources.ComputerAccessDenied,
2288+
ErrorCategory.PermissionDenied);
2289+
continue;
2290+
}
2291+
hService = NativeMethods.OpenServiceW(
2292+
hScManager,
2293+
Name,
2294+
NativeMethods.SERVICE_DELETE
2295+
);
2296+
if (IntPtr.Zero == hService)
2297+
{
2298+
int lastError = Marshal.GetLastWin32Error();
2299+
Win32Exception exception = new Win32Exception(lastError);
2300+
WriteNonTerminatingError(
2301+
service,
2302+
exception,
2303+
"CouldNotRemoveService",
2304+
ServiceResources.CouldNotSetService,
2305+
ErrorCategory.PermissionDenied);
2306+
continue;
2307+
}
2308+
2309+
bool status = NativeMethods.DeleteService(hService);
2310+
2311+
if (!status)
2312+
{
2313+
int lastError = Marshal.GetLastWin32Error();
2314+
Win32Exception exception = new Win32Exception(lastError);
2315+
WriteNonTerminatingError(
2316+
service,
2317+
exception,
2318+
"CouldNotRemoveService",
2319+
ServiceResources.CouldNotRemoveService,
2320+
ErrorCategory.PermissionDenied);
2321+
}
2322+
}
2323+
finally
2324+
{
2325+
if (IntPtr.Zero != hService)
2326+
{
2327+
bool succeeded = NativeMethods.CloseServiceHandle(hService);
2328+
if (!succeeded)
2329+
{
2330+
int lastError = Marshal.GetLastWin32Error();
2331+
Diagnostics.Assert(lastError != 0, "ErrorCode not success");
2332+
}
2333+
}
2334+
2335+
if (IntPtr.Zero != hScManager)
2336+
{
2337+
bool succeeded = NativeMethods.CloseServiceHandle(hScManager);
2338+
if (!succeeded)
2339+
{
2340+
int lastError = Marshal.GetLastWin32Error();
2341+
Diagnostics.Assert(lastError != 0, "ErrorCode not success");
2342+
}
2343+
}
2344+
} // Finally
2345+
} // End try
2346+
finally
2347+
{
2348+
if (objServiceShouldBeDisposed)
2349+
{
2350+
service.Dispose();
2351+
}
2352+
}
2353+
}// End for
2354+
}
2355+
#endregion Overrides
2356+
} // class RemoveServiceCommand
2357+
#endregion RemoveServiceCommand
2358+
21702359
#region ServiceCommandException
21712360
/// <summary>
21722361
/// Non-terminating errors occurring in the service noun commands
@@ -2264,8 +2453,10 @@ internal static class NativeMethods
22642453
internal const int ERROR_SERVICE_NOT_ACTIVE = 1062;
22652454
internal const DWORD SC_MANAGER_CONNECT = 1;
22662455
internal const DWORD SC_MANAGER_CREATE_SERVICE = 2;
2456+
internal const DWORD SC_MANAGER_ALL_ACCESS = 0xf003f;
22672457
internal const DWORD SERVICE_QUERY_CONFIG = 1;
22682458
internal const DWORD SERVICE_CHANGE_CONFIG = 2;
2459+
internal const DWORD SERVICE_DELETE = 0x10000;
22692460
internal const DWORD SERVICE_NO_CHANGE = 0xffffffff;
22702461
internal const DWORD SERVICE_AUTO_START = 0x2;
22712462
internal const DWORD SERVICE_DEMAND_START = 0x3;
@@ -2297,6 +2488,12 @@ bool CloseServiceHandle(
22972488
NakedWin32Handle hSCManagerOrService
22982489
);
22992490

2491+
[DllImport(PinvokeDllNames.DeleteServiceDllName, CharSet = CharSet.Unicode, SetLastError = true)]
2492+
internal static extern
2493+
bool DeleteService(
2494+
NakedWin32Handle hService
2495+
);
2496+
23002497
[DllImport(PinvokeDllNames.ChangeServiceConfigWDllName, CharSet = CharSet.Unicode, SetLastError = true)]
23012498
internal static extern
23022499
bool ChangeServiceConfigW(

src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@
171171
<data name="CouldNotNewServiceDescription" xml:space="preserve">
172172
<value>Service '{1} ({0})' was created, but its description cannot be configured due to the following error: {2}</value>
173173
</data>
174+
<data name="CouldNotRemoveService" xml:space="preserve">
175+
<value>Service '{1} ({0})' cannot be removed due to the following error: {2}</value>
176+
</data>
174177
<data name="CouldNotAccessDependentServices" xml:space="preserve">
175178
<value>'Cannot access dependent services of '{1} ({0})'</value>
176179
</data>

src/Modules/Windows-Core/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ CmdletsToExport=@("Add-Content",
5656
"Restart-Service",
5757
"Set-Service",
5858
"New-Service",
59+
"Remove-Service",
5960
"Set-Content",
6061
"Set-ItemProperty",
6162
"Test-Connection",

src/Modules/Windows-Full/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ CmdletsToExport=@("Add-Content",
6868
"Restart-Service",
6969
"Set-Service",
7070
"New-Service",
71+
"Remove-Service",
7172
"Set-Content",
7273
"Set-ItemProperty",
7374
"Set-WmiInstance",

src/System.Management.Automation/utils/PInvokeDllNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,6 @@ internal static class PinvokeDllNames
135135
internal const string Process32FirstDllName = "api-ms-win-core-toolhelp-l1-1-0"; /*121*/
136136
internal const string Process32NextDllName = "api-ms-win-core-toolhelp-l1-1-0"; /*122*/
137137
internal const string GetACPDllName = "api-ms-win-core-localization-l1-2-0.dll"; /*123*/
138+
internal const string DeleteServiceDllName = "api-ms-win-service-management-l1-1-0.dll"; /*124*/
138139
}
139140
}

test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Describe "Set/New-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows" {
1+
Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows" {
22
BeforeAll {
33
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
44
if ( -not $IsWindows ) {
@@ -195,6 +195,65 @@ Describe "Set/New-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows"
195195
}
196196
}
197197

198+
It "Remove-Service can remove a service" {
199+
try {
200+
$servicename = "testremoveservice"
201+
$parameters = @{
202+
Name = $servicename;
203+
BinaryPathName = "$PSHOME\powershell.exe"
204+
}
205+
$service = New-Service @parameters
206+
$service | Should Not BeNullOrEmpty
207+
Remove-Service -Name $servicename
208+
$service = Get-Service -Name $servicename -ErrorAction SilentlyContinue
209+
$service | Should BeNullOrEmpty
210+
}
211+
finally {
212+
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
213+
}
214+
}
215+
216+
It "Remove-Service can accept pipeline input of a ServiceController" {
217+
try {
218+
$servicename = "testremoveservice"
219+
$parameters = @{
220+
Name = $servicename;
221+
BinaryPathName = "$PSHOME\powershell.exe"
222+
}
223+
$service = New-Service @parameters
224+
$service | Should Not BeNullOrEmpty
225+
Get-Service -Name $servicename | Remove-Service
226+
$service = Get-Service -Name $servicename -ErrorAction SilentlyContinue
227+
$service | Should BeNullOrEmpty
228+
}
229+
finally {
230+
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
231+
}
232+
}
233+
234+
It "Remove-Service cannot accept a service that does not exist" {
235+
{ Remove-Service -Name "testremoveservice" -ErrorAction 'Stop' } | ShouldBeErrorId "InvalidOperationException,Microsoft.PowerShell.Commands.RemoveServiceCommand"
236+
}
237+
238+
It "Set-Service can accept pipeline input of a ServiceController" {
239+
try {
240+
$servicename = "testsetservice"
241+
$newdisplayname = "newdisplayname"
242+
$parameters = @{
243+
Name = $servicename;
244+
BinaryPathName = "$PSHOME\powershell.exe"
245+
}
246+
$service = New-Service @parameters
247+
$service | Should Not BeNullOrEmpty
248+
Get-Service -Name $servicename | Set-Service -DisplayName $newdisplayname
249+
$service = Get-Service -Name $servicename
250+
$service.DisplayName | Should BeExactly $newdisplayname
251+
}
252+
finally {
253+
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
254+
}
255+
}
256+
198257
It "Using bad parameters will fail for '<name>' where '<parameter>' = '<value>'" -TestCases @(
199258
@{cmdlet="New-Service"; name = 'credtest' ; parameter = "Credential" ; value = (
200259
[System.Management.Automation.PSCredential]::new("username",

test/powershell/engine/Basic/DefaultCommands.Tests.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ Describe "Verify approved aliases list" -Tags "CI" {
374374
"Cmdlet", "Remove-PSDrive", , $($FullCLR -or $CoreWindows -or $CoreUnix)
375375
"Cmdlet", "Remove-PSSession", , $($FullCLR -or $CoreWindows -or $CoreUnix)
376376
"Cmdlet", "Remove-PSSnapin", , $($FullCLR )
377+
"Cmdlet", "Remove-Service", , $($FullCLR -or $CoreWindows )
377378
"Cmdlet", "Remove-TypeData", , $($FullCLR -or $CoreWindows -or $CoreUnix)
378379
"Cmdlet", "Remove-Variable", , $($FullCLR -or $CoreWindows -or $CoreUnix)
379380
"Cmdlet", "Remove-WmiObject", , $($FullCLR )

0 commit comments

Comments
 (0)