mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
426 lines
20 KiB
PowerShell
426 lines
20 KiB
PowerShell
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT License.
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
|
|
param()
|
|
|
|
Import-Module (Join-Path -Path $PSScriptRoot '..\Microsoft.PowerShell.Security\certificateCommon.psm1')
|
|
|
|
Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows" {
|
|
BeforeAll {
|
|
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
|
if ( -not $IsWindows ) {
|
|
$PSDefaultParameterValues["it:skip"] = $true
|
|
}
|
|
if ($IsWindows) {
|
|
$userName = "testuserservices"
|
|
$testPass = [Net.NetworkCredential]::new("", (New-ComplexPassword)).SecurePassword
|
|
$creds = [pscredential]::new(".\$userName", $testPass)
|
|
$SecurityDescriptorSddl = 'D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;SU)'
|
|
$WrongSecurityDescriptorSddl = 'D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BB)(A;;CCLCSWLOCRRC;;;SU)'
|
|
net user $userName $creds.GetNetworkCredential().Password /add > $null
|
|
|
|
$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
|
|
}
|
|
|
|
Function CheckSecurityDescriptorSddl {
|
|
Param(
|
|
[Parameter(Mandatory)]
|
|
$SecurityDescriptorSddl,
|
|
|
|
[Parameter(Mandatory)]
|
|
$ServiceName
|
|
)
|
|
$Counter = 0
|
|
$ExpectedSDDL = ConvertFrom-SddlString -Sddl $SecurityDescriptorSddl
|
|
|
|
# Selecting the first item in the output array as below command gives plain text output from the native sc.exe.
|
|
$UpdatedSDDL = ConvertFrom-SddlString -Sddl (sc sdshow $ServiceName)[1]
|
|
|
|
$UpdatedSDDL.Owner | Should -Be $ExpectedSDDL.Owner
|
|
$UpdatedSDDL.Group | Should -Be $ExpectedSDDL.Group
|
|
$UpdatedSDDL.DiscretionaryAcl.Count | Should -Be $ExpectedSDDL.DiscretionaryAcl.Count
|
|
$UpdatedSDDL.DiscretionaryAcl | ForEach-Object -Process {
|
|
$_ | Should -Be $ExpectedSDDL.DiscretionaryAcl[$Counter]
|
|
$Counter++
|
|
}
|
|
}
|
|
}
|
|
AfterAll {
|
|
$global:PSDefaultParameterValues = $originalDefaultParameterValues
|
|
if ($IsWindows) {
|
|
net user $userName /delete > $null
|
|
|
|
Stop-Service $testservicename2
|
|
Stop-Service $testservicename1
|
|
Remove-Service $testservicename2
|
|
Remove-Service $testservicename1
|
|
}
|
|
}
|
|
|
|
It "SetServiceCommand can be used as API for '<parameter>' with '<value>'" -TestCases @(
|
|
@{parameter = "Name" ; value = "bar"},
|
|
@{parameter = "DisplayName" ; value = "hello"},
|
|
@{parameter = "Description" ; value = "hello world"},
|
|
@{parameter = "StartupType" ; value = "Automatic"},
|
|
@{parameter = "StartupType" ; value = "Disabled"},
|
|
@{parameter = "StartupType" ; value = "Manual"},
|
|
@{parameter = "Status" ; value = "Running"},
|
|
@{parameter = "Status" ; value = "Stopped"},
|
|
@{parameter = "Status" ; value = "Paused"},
|
|
@{parameter = "InputObject" ; script = {Get-Service | Select-Object -First 1}},
|
|
# cmdlet inherits this property, but it's not exposed as parameter so it should be $null
|
|
@{parameter = "Include" ; value = "foo", "bar" ; expectedNull = $true},
|
|
# cmdlet inherits this property, but it's not exposed as parameter so it should be $null
|
|
@{parameter = "Exclude" ; value = "foo", "bar" ; expectedNull = $true}
|
|
) {
|
|
param($parameter, $value, $script, $expectedNull)
|
|
|
|
$setServiceCommand = [Microsoft.PowerShell.Commands.SetServiceCommand]::new()
|
|
if ($script -ne $null) {
|
|
$value = & $script
|
|
}
|
|
$setServiceCommand.$parameter = $value
|
|
if ($expectedNull -eq $true) {
|
|
$setServiceCommand.$parameter | Should -BeNullOrEmpty
|
|
}
|
|
else {
|
|
$setServiceCommand.$parameter | Should -Be $value
|
|
}
|
|
}
|
|
|
|
It "Set-Service parameter validation for invalid values: <script>" -TestCases @(
|
|
@{
|
|
script = {Set-Service foo -StartupType bar -ErrorAction Stop};
|
|
errorid = "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SetServiceCommand"
|
|
},
|
|
@{
|
|
script = {Set-Service -Name $testservicename1 -SecurityDescriptorSddl $WrongSecurityDescriptorSddl };
|
|
errorid = "System.ArgumentException,Microsoft.PowerShell.Commands.SetServiceCommand"
|
|
}
|
|
) {
|
|
param($script, $errorid)
|
|
{ & $script } | Should -Throw -ErrorId $errorid
|
|
}
|
|
|
|
|
|
It "Sets securitydescriptor of service using Set-Service " {
|
|
Set-Service -Name $TestServiceName1 -SecurityDescriptorSddl $SecurityDescriptorSddl
|
|
CheckSecurityDescriptorSddl -SecurityDescriptor $SecurityDescriptorSddl -ServiceName $TestServiceName1
|
|
}
|
|
|
|
It "Set-Service can change '<parameter>' to '<value>'" -TestCases @(
|
|
@{parameter = "Description"; value = "hello"},
|
|
@{parameter = "DisplayName"; value = "test spooler"},
|
|
@{parameter = "StartupType"; value = "Disabled"},
|
|
@{parameter = "Status" ; value = "running" ; expected = "OK"}
|
|
) {
|
|
param($parameter, $value, $expected)
|
|
$currentService = Get-CimInstance -ClassName Win32_Service -Filter "Name='spooler'"
|
|
$originalStartupType = (Get-Service -Name spooler).StartType
|
|
try {
|
|
$setServiceCommand = [Microsoft.PowerShell.Commands.SetServiceCommand]::new()
|
|
$setServiceCommand.Name = "Spooler"
|
|
$setServiceCommand.$parameter = $value
|
|
$setServiceCommand.Invoke()
|
|
$updatedService = Get-CimInstance -ClassName Win32_Service -Filter "Name='spooler'"
|
|
if ($expected -eq $null) {
|
|
$expected = $value
|
|
}
|
|
if ($parameter -eq "StartupType") {
|
|
$updatedService.StartMode | Should -Be $expected
|
|
}
|
|
else {
|
|
$updatedService.$parameter | Should -Be $expected
|
|
}
|
|
}
|
|
finally {
|
|
if ($parameter -eq "StartupType") {
|
|
$setServiceCommand.StartupType = $originalStartupType
|
|
}
|
|
else {
|
|
$setServiceCommand.$parameter = $currentService.$parameter
|
|
}
|
|
$setServiceCommand.Invoke()
|
|
$updatedService = Get-CimInstance -ClassName Win32_Service -Filter "Name='spooler'"
|
|
$updatedService.$parameter | Should -Be $currentService.$parameter
|
|
}
|
|
}
|
|
|
|
It "NewServiceCommand can be used as API for '<parameter>' with '<value>'" -TestCases @(
|
|
@{parameter = "Name" ; value = "bar"},
|
|
@{parameter = "BinaryPathName" ; value = "hello"},
|
|
@{parameter = "DisplayName" ; value = "hello world"},
|
|
@{parameter = "Description" ; value = "this is a test"},
|
|
@{parameter = "StartupType" ; value = "Automatic"},
|
|
@{parameter = "StartupType" ; value = "Disabled"},
|
|
@{parameter = "StartupType" ; value = "Manual"},
|
|
@{parameter = "SecurityDescriptorSddl" ; value = $SecurityDescriptorSddl},
|
|
@{parameter = "Credential" ; value = (
|
|
[System.Management.Automation.PSCredential]::new("username",
|
|
#[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Demo/doc/test secret.")]
|
|
(ConvertTo-SecureString "PlainTextPassword" -AsPlainText -Force)))
|
|
}
|
|
@{parameter = "DependsOn" ; value = "foo", "bar"}
|
|
) {
|
|
param($parameter, $value)
|
|
|
|
$newServiceCommand = [Microsoft.PowerShell.Commands.NewServiceCommand]::new()
|
|
$newServiceCommand.$parameter = $value
|
|
$newServiceCommand.$parameter | Should -Be $value
|
|
}
|
|
|
|
It "Set-Service can change credentials of a service" -Pending {
|
|
try {
|
|
$startUsername = "user1"
|
|
$endUsername = "user2"
|
|
$servicename = "testsetcredential"
|
|
$testPass = [Net.NetworkCredential]::new("", (New-ComplexPassword)).SecurePassword
|
|
$creds = [pscredential]::new(".\$endUsername", $testPass)
|
|
net user $startUsername $creds.GetNetworkCredential().Password /add > $null
|
|
net user $endUsername $creds.GetNetworkCredential().Password /add > $null
|
|
$parameters = @{
|
|
Name = $servicename;
|
|
BinaryPathName = "$PSHOME\pwsh.exe";
|
|
StartupType = "Manual";
|
|
Credential = $creds
|
|
}
|
|
$service = New-Service @parameters
|
|
$service | Should -Not -BeNullOrEmpty
|
|
$service = Get-CimInstance Win32_Service -Filter "name='$servicename'"
|
|
$service.StartName | Should -BeExactly $creds.UserName
|
|
|
|
Set-Service -Name $servicename -Credential $creds
|
|
$service = Get-CimInstance Win32_Service -Filter "name='$servicename'"
|
|
$service.StartName | Should -BeExactly $creds.UserName
|
|
}
|
|
finally {
|
|
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
|
|
net user $startUsername /delete > $null
|
|
net user $endUsername /delete > $null
|
|
}
|
|
}
|
|
|
|
It "New-Service can create a new service called '<name>'" -TestCases @(
|
|
@{name = "testautomatic"; startupType = "Automatic"; description = "foo" ; displayname = "one" ; securityDescriptorSddl = $null},
|
|
@{name = "testmanual" ; startupType = "Manual" ; description = "bar" ; displayname = "two" ; securityDescriptorSddl = $SecurityDescriptorSddl},
|
|
@{name = "testdisabled" ; startupType = "Disabled" ; description = $null ; displayname = $null ; securityDescriptorSddl = $null},
|
|
@{name = "testsddl" ; startupType = "Disabled" ; description = "foo" ; displayname = $null ; securityDescriptorSddl = $SecurityDescriptorSddl}
|
|
) {
|
|
param($name, $startupType, $description, $displayname, $securityDescriptorSddl)
|
|
try {
|
|
$parameters = @{
|
|
Name = $name;
|
|
BinaryPathName = "$PSHOME\pwsh.exe";
|
|
StartupType = $startupType;
|
|
}
|
|
if ($description) {
|
|
$parameters += @{description = $description}
|
|
}
|
|
if ($displayname) {
|
|
$parameters += @{displayname = $displayname}
|
|
}
|
|
if ($securityDescriptorSddl) {
|
|
$parameters += @{SecurityDescriptorSddl = $securityDescriptorSddl}
|
|
}
|
|
|
|
$service = New-Service @parameters
|
|
$service | Should -Not -BeNullOrEmpty
|
|
$service.displayname | Should -Be $(if($displayname){$displayname}else{$name})
|
|
$service.startType | Should -Be $startupType
|
|
|
|
$service = Get-CimInstance Win32_Service -Filter "name='$name'"
|
|
$service | Should -Not -BeNullOrEmpty
|
|
$service.Name | Should -Be $name
|
|
$service.Description | Should -Be $description
|
|
$expectedStartup = $(
|
|
switch ($startupType) {
|
|
"Automatic" {"Auto"}
|
|
"Manual" {"Manual"}
|
|
"Disabled" {"Disabled"}
|
|
default { throw "Unsupported StartupType in TestCases" }
|
|
}
|
|
)
|
|
$service.StartMode | Should -Be $expectedStartup
|
|
if ($displayname -eq $null) {
|
|
$service.DisplayName | Should -Be $name
|
|
}
|
|
else {
|
|
$service.DisplayName | Should -Be $displayname
|
|
}
|
|
if ($securityDescriptorSddl) {
|
|
CheckSecurityDescriptorSddl -SecurityDescriptorSddl $SecurityDescriptorSddl -ServiceName $name
|
|
}
|
|
}
|
|
finally {
|
|
$service = Get-CimInstance Win32_Service -Filter "name='$name'"
|
|
if ($service -ne $null) {
|
|
$service | Remove-CimInstance
|
|
}
|
|
}
|
|
}
|
|
|
|
It "Remove-Service can remove a service" {
|
|
try {
|
|
$servicename = "testremoveservice"
|
|
$parameters = @{
|
|
Name = $servicename;
|
|
BinaryPathName = "$PSHOME\pwsh.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 a ServiceController as pipeline input" {
|
|
try {
|
|
$servicename = "testremoveservice"
|
|
$parameters = @{
|
|
Name = $servicename;
|
|
BinaryPathName = "$PSHOME\pwsh.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' } | Should -Throw -ErrorId "InvalidOperationException,Microsoft.PowerShell.Commands.RemoveServiceCommand"
|
|
}
|
|
|
|
It "Get-Service can get the '<property>' of a service" -Pending -TestCases @(
|
|
@{property = "Description"; value = "This is a test description"}
|
|
@{property = "BinaryPathName"; value = "$PSHOME\powershell.exe";},
|
|
@{property = "UserName"; value = $creds.UserName; parameters = @{ Credential = $creds }},
|
|
@{property = "StartupType"; value = "AutomaticDelayedStart";}
|
|
) {
|
|
param($property, $value, $parameters)
|
|
try {
|
|
$servicename = "testgetservice"
|
|
$startparameters = @{Name = $servicename; BinaryPathName = "$PSHOME\powershell.exe"}
|
|
if($parameters -ne $null) {
|
|
foreach($key in $parameters.Keys) {
|
|
$startparameters.$key = $parameters.$key
|
|
}
|
|
} else {
|
|
$startparameters.$property = $value
|
|
}
|
|
$service = New-Service @startparameters
|
|
$service | Should -Not -BeNullOrEmpty
|
|
$service = Get-Service -Name $servicename
|
|
$service.$property | Should -BeExactly $value
|
|
}
|
|
finally {
|
|
Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
It "Set-Service can accept a ServiceController as pipeline input" {
|
|
try {
|
|
$servicename = "testsetservice"
|
|
$newdisplayname = "newdisplayname"
|
|
$parameters = @{
|
|
Name = $servicename;
|
|
BinaryPathName = "$PSHOME\pwsh.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 "Set-Service can accept a ServiceController as positional input" {
|
|
try {
|
|
$servicename = "testsetservice"
|
|
$newdisplayname = "newdisplayname"
|
|
$parameters = @{
|
|
Name = $servicename;
|
|
BinaryPathName = "$PSHOME\pwsh.exe"
|
|
}
|
|
|
|
$script = { New-Service @parameters | Set-Service -DisplayName $newdisplayname }
|
|
{ & $script } | Should -Not -Throw
|
|
$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",
|
|
#[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Demo/doc/test secret.")]
|
|
(ConvertTo-SecureString "PlainTextPassword" -AsPlainText -Force)));
|
|
errorid = "CouldNotNewService,Microsoft.PowerShell.Commands.NewServiceCommand"},
|
|
@{cmdlet="New-Service"; name = 'badstarttype'; parameter = "StartupType"; value = "System";
|
|
errorid = "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.NewServiceCommand"},
|
|
@{cmdlet="New-Service"; name = 'winmgmt' ; parameter = "DisplayName"; value = "foo";
|
|
errorid = "CouldNotNewService,Microsoft.PowerShell.Commands.NewServiceCommand"},
|
|
@{cmdlet="Set-Service"; name = 'winmgmt' ; parameter = "StartupType"; value = "Boot";
|
|
errorid = "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SetServiceCommand"}
|
|
) {
|
|
param($cmdlet, $name, $parameter, $value, $errorid)
|
|
$parameters = @{$parameter = $value; Name = $name; ErrorAction = "Stop"}
|
|
if ($cmdlet -eq "New-Service") {
|
|
$parameters += @{Binary = "$PSHOME\pwsh.exe"};
|
|
}
|
|
{ & $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"
|
|
}
|
|
}
|
|
}
|