mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
[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:
committed by
Aditya Patwardhan
parent
4c6ad4a133
commit
f281671375
@@ -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>
|
||||
|
||||
+1
@@ -56,6 +56,7 @@ CmdletsToExport=@("Add-Content",
|
||||
"Restart-Service",
|
||||
"Set-Service",
|
||||
"New-Service",
|
||||
"Remove-Service",
|
||||
"Set-Content",
|
||||
"Set-ItemProperty",
|
||||
"Test-Connection",
|
||||
|
||||
+1
@@ -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 )
|
||||
|
||||
Reference in New Issue
Block a user