Tests/Unit/MSFT_xServiceResource.Tests.ps1
# Need to be able to create a password from plain text to create test credentials [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () $errorActionPreference = 'Stop' Set-StrictMode -Version 'Latest' # Import CommonTestHelper for Enter-DscResourceTestEnvironment, Exit-DscResourceTestEnvironment $script:testsFolderFilePath = Split-Path $PSScriptRoot -Parent $script:commonTestHelperFilePath = Join-Path -Path $testsFolderFilePath -ChildPath 'CommonTestHelper.psm1' Import-Module -Name $commonTestHelperFilePath if (Test-SkipContinuousIntegrationTask -Type 'Unit') { return } $script:testEnvironment = Enter-DscResourceTestEnvironment ` -DSCResourceModuleName 'xPSDesiredStateConfiguration' ` -DSCResourceName 'MSFT_xServiceResource' ` -TestType 'Unit' try { # This is needed so that the ServiceControllerStatus enum is recognized as a valid type Add-Type -AssemblyName 'System.ServiceProcess' InModuleScope 'MSFT_xServiceResource' { $script:testServiceName = 'DscTestService' $script:testUsername1 = 'TestUser1' $script:testUsername2 = 'TestUser2' $script:testPassword = 'DummyPassword' $secureTestPassword = ConvertTo-SecureString $script:testPassword -AsPlainText -Force $script:testCredential1 = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList ($script:testUsername1, $secureTestPassword) $script:testCredential2 = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList ($script:testUsername2, $secureTestPassword) $script:gMSAUser1 = 'DOMAIN\gMSA1$' $script:gMSAUser2 = 'DOMAIN\gMSA2$' Describe 'xService\Get-TargetResource' { <# .SYNOPSIS Invokes Get-TargetResource, ensures that it does not throw an exception, and tests whether expected functions were called. .PARAMETER GetTargetResourceParameters The parameters to pass to Get-TargetResource .PARAMETER TestServiceCimInstance The TestServiceCimInstance object to use when checking whether ConvertTo-StartupTypeString was called. .PARAMETER ExpectServiceCIMInstance Whether or not the function should expect Get-ServiceCimInstance to be called. Defaults to $true. #> function Test-GetTargetResourceDoesntThrow { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $GetTargetResourceParameters, [Parameter()] [System.Collections.Hashtable] $TestServiceCimInstance, [Parameter()] [System.Boolean] $ExpectServiceCIMInstance = $true ) It 'Should not throw' { { $null = Get-TargetResource @GetTargetResourceParameters } | Should -Not -Throw } It 'Should retrieve service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $GetTargetResourceParameters.Name } -Times 1 -Scope 'Context' } if ($ExpectServiceCIMInstance) { $expectedTimes = 1 } else { $expectedTimes = 0 } It 'Should retrieve the service CIM instance' { Assert-MockCalled 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $GetTargetResourceParameters.Name } -Times $expectedTimes -Scope 'Context' } It 'Should convert the service start mode to a startup type string' { Assert-MockCalled -CommandName 'ConvertTo-StartupTypeString' -ParameterFilter { $StartMode -eq $TestServiceCimInstance.StartMode } -Times $expectedTimes -Scope 'Context' } } <# .SYNOPSIS Invokes Get-TargetResource, and performs tests against the return variable. .PARAMETER GetTargetResourceParameters The parameters to pass to Get-TargetResource .PARAMETER ExpectedValues A hashtable containing values that are expected to be returned by Get-TargetResource. #> function Test-GetTargetResourceResult { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $GetTargetResourceParameters, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $ExpectedValues ) $getTargetResourceResult = Get-TargetResource @GetTargetResourceParameters It 'Should return a hashtable' { $getTargetResourceResult -is [System.Collections.Hashtable] | Should -Be $true } if ($ExpectedValues.ContainsKey('Name')) { It 'Should return the service name' { $getTargetResourceResult.Name | Should -Be $ExpectedValues.Name } } if ($ExpectedValues.ContainsKey('Ensure')) { It 'Should return the service Ensure state as Present' { $getTargetResourceResult.Ensure | Should -Be $ExpectedValues.Ensure } } if ($ExpectedValues.ContainsKey('Path')) { It 'Should return the service path' { $getTargetResourceResult.Path | Should -Be $ExpectedValues.Path } } if ($ExpectedValues.ContainsKey('StartupType')) { It 'Should return the service startup type' { $getTargetResourceResult.StartupType | Should -Be $ExpectedValues.StartupType } } if ($ExpectedValues.ContainsKey('BuiltInAccount')) { It 'Should return the service startup account name' { $getTargetResourceResult.BuiltInAccount | Should -Be $ExpectedValues.BuiltInAccount } } if ($ExpectedValues.ContainsKey('State')) { It 'Should return the service state' { $getTargetResourceResult.State | Should -Be $ExpectedValues.State } } if ($ExpectedValues.ContainsKey('DisplayName')) { It 'Should return the service display name' { $getTargetResourceResult.DisplayName | Should -Be $ExpectedValues.DisplayName } } if ($ExpectedValues.ContainsKey('Description')) { It 'Should return the service description as null' { $getTargetResourceResult.Description | Should -Be $ExpectedValues.Description } } if ($ExpectedValues.ContainsKey('DesktopInteract')) { It 'Should return the service desktop interation setting' { $getTargetResourceResult.DesktopInteract | Should -Be $ExpectedValues.DesktopInteract } } if ($ExpectedValues.ContainsKey('Dependencies')) { It 'Should return the service dependencies' { $getTargetResourceResult.Dependencies | Should -Be $ExpectedValues.Dependencies } } } Mock -CommandName 'Get-Service' -MockWith { } Mock -CommandName 'Get-ServiceCimInstance' -MockWith { } Mock -CommandName 'ConvertTo-StartupTypeString' -MockWith { } $getTargetResourceParameters = @{ Name = 'TestServiceName' } $convertToStartupTypeStringResult = 'TestStartupTypeString' Context 'When a service does not exist' { Test-GetTargetResourceDoesntThrow -GetTargetResourceParameters $getTargetResourceParameters -ExpectServiceCIMInstance $false $expectedValues = @{ Name = $getTargetResourceParameters.Name Ensure = 'Absent' } Test-GetTargetResourceResult -GetTargetResourceParameters $getTargetResourceParameters -ExpectedValues $expectedValues } Context 'When a service exists with all properties defined and custom startup account name' { $testService = @{ Name = 'TestServiceName' DisplayName = 'TestDisplayName' Status = 'TestServiceStatus' StartType = 'TestServiceStartType' ServicesDependedOn = @( @{ Name = 'ServiceDependency1' }, @{ Name = 'ServiceDependency2' } ) } $testServiceCimInstance = @{ Name = $testService.Name PathName = 'TestServicePath' Description = 'Test service description' StartName = 'CustomStartName' StartMode = 'Auto' DesktopInteract = $true } Mock -CommandName 'Get-Service' -MockWith { return $testService } Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'ConvertTo-StartupTypeString' -MockWith { return $convertToStartupTypeStringResult } Test-GetTargetResourceDoesntThrow -GetTargetResourceParameters $getTargetResourceParameters -TestServiceCimInstance $testServiceCimInstance $expectedValues = @{ Name = $getTargetResourceParameters.Name Ensure = 'Present' Path = $testServiceCimInstance.PathName StartupType = $convertToStartupTypeStringResult BuiltInAccount = $testServiceCimInstance.StartName State = $testService.Status DisplayName = $testService.DisplayName Description = $testServiceCimInstance.Description DesktopInteract = $testServiceCimInstance.DesktopInteract Dependencies = [System.Object[]] $testService.ServicesDependedOn.Name } Test-GetTargetResourceResult -GetTargetResourceParameters $getTargetResourceParameters -ExpectedValues $expectedValues } Context 'When a service exists with no dependencies and startup account name as NT Authority\LocalService' { $testService = @{ Name = 'TestServiceName' DisplayName = 'TestDisplayName' Status = 'TestServiceStatus' StartType = 'TestServiceStartType' ServicesDependedOn = $null } $expectedBuiltInAccountValue = 'LocalService' $testServiceCimInstance = @{ Name = $testService.Name PathName = 'TestServicePath' Description = 'Test service description' StartName = "NT Authority\$expectedBuiltInAccountValue" StartMode = 'Manual' DesktopInteract = $false } Mock -CommandName 'Get-Service' -MockWith { return $testService } Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'ConvertTo-StartupTypeString' -MockWith { return $convertToStartupTypeStringResult } Test-GetTargetResourceDoesntThrow -GetTargetResourceParameters $getTargetResourceParameters -TestServiceCimInstance $testServiceCimInstance $expectedValues = @{ Name = $getTargetResourceParameters.Name Ensure = 'Present' Path = $testServiceCimInstance.PathName StartupType = $convertToStartupTypeStringResult BuiltInAccount = $expectedBuiltInAccountValue State = $testService.Status DisplayName = $testService.DisplayName Description = $testServiceCimInstance.Description DesktopInteract = $testServiceCimInstance.DesktopInteract Dependencies = $null } Test-GetTargetResourceResult -GetTargetResourceParameters $getTargetResourceParameters -ExpectedValues $expectedValues } Context 'When a service exists with no description or display name and startup account name as NT Authority\NetworkService' { $testService = @{ Name = 'TestServiceName' DisplayName = $null Status = 'TestServiceStatus' StartType = 'TestServiceStartType' ServicesDependedOn = @( @{ Name = 'ServiceDependency1' }, @{ Name = 'ServiceDependency2' } ) } $expectedBuiltInAccountValue = 'NetworkService' $testServiceCimInstance = @{ Name = $testService.Name PathName = 'TestServicePath' Description = $null StartName = "NT Authority\$expectedBuiltInAccountValue" StartMode = 'Disabled' DesktopInteract = $false } Mock -CommandName 'Get-Service' -MockWith { return $testService } Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'ConvertTo-StartupTypeString' -MockWith { return $convertToStartupTypeStringResult } Test-GetTargetResourceDoesntThrow -GetTargetResourceParameters $getTargetResourceParameters -TestServiceCimInstance $testServiceCimInstance $expectedValues = @{ Name = $getTargetResourceParameters.Name Ensure = 'Present' Path = $testServiceCimInstance.PathName StartupType = $convertToStartupTypeStringResult BuiltInAccount = $expectedBuiltInAccountValue State = $testService.Status DisplayName = $null Description = $null DesktopInteract = $testServiceCimInstance.DesktopInteract Dependencies = [System.Object[]] $testService.ServicesDependedOn.Name } Test-GetTargetResourceResult -GetTargetResourceParameters $getTargetResourceParameters -ExpectedValues $expectedValues } Context 'When a service exists with with stale or corrupt dependencies' { <# Due to a failed install or uninstall, it's possible to get in a scenario where a service has a dependency configured in the registry (in the DependOnService REG_MULTI_SZ value), but where the specified service no longer exists. When this occurs, Get-Service will return a member with null properties within the ServicesDependedOn property for the missing service. Get-TargetResource should be able to handle this. Here's an example where XboxNetApiSvc has a dependency on BADSVC, which no longer exists as an actual service: PS D:\> (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\XboxNetApiSvc' -Name 'DependOnService').DependOnService BFE mpssvc IKEEXT KeyIso BADSVC PS D:\> $s = Get-Service XboxNetApiSvc PS D:\> $s.ServicesDependedOn Status Name DisplayName ------ ---- ----------- Running KeyIso CNG Key Isolation Running IKEEXT IKE and AuthIP IPsec Keying Modules Running mpssvc Windows Defender Firewall Running BFE Base Filtering Engine PS D:\> $s.ServicesDependedOn[0] Status Name DisplayName ------ ---- ----------- PS D:\> $s.ServicesDependedOn[0].Name -eq $null True #> $testService = @{ Name = 'TestServiceName' DisplayName = 'TestDisplayName' Status = 'TestServiceStatus' StartType = 'TestServiceStartType' ServicesDependedOn = @( @{ Name = 'ServiceDependency1' }, @{ Name = $null } ) } $testServiceCimInstance = @{ Name = $testService.Name PathName = 'TestServicePath' Description = 'Test service description' StartName = 'LocalService' StartMode = 'Auto' DesktopInteract = $false } Mock -CommandName 'Get-Service' -MockWith { return $testService } Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'ConvertTo-StartupTypeString' -MockWith { return $convertToStartupTypeStringResult } Mock -CommandName 'Write-Warning' Test-GetTargetResourceDoesntThrow -GetTargetResourceParameters $getTargetResourceParameters -TestServiceCimInstance $testServiceCimInstance It 'Should warn that a service dependency is corrupt' { Assert-MockCalled -CommandName 'Write-Warning' -ParameterFilter { $Message -like "*has a corrupt dependency*" } -Times 1 -Scope 'Context' } $expectedValues = @{ Name = $getTargetResourceParameters.Name Ensure = 'Present' Path = $testServiceCimInstance.PathName StartupType = $convertToStartupTypeStringResult BuiltInAccount = $testServiceCimInstance.StartName State = $testService.Status DisplayName = $testService.DisplayName Description = $testServiceCimInstance.Description DesktopInteract = $testServiceCimInstance.DesktopInteract Dependencies = [System.Object[]] ($testService.ServicesDependedOn | Where-Object -FilterScript {![System.String]::IsNullOrEmpty($_.Name)}).Name } Test-GetTargetResourceResult -GetTargetResourceParameters $getTargetResourceParameters -ExpectedValues $expectedValues } } Describe 'xService\Set-TargetResource' { Mock -CommandName 'Assert-NoStartupTypeStateConflict' -MockWith { } Mock -CommandName 'Get-Service' -MockWith { } Mock -CommandName 'New-Service' -MockWith { } Mock -CommandName 'Remove-ServiceWithTimeout' -MockWith { } Mock -CommandName 'Set-ServicePath' -MockWith { return $true } Mock -CommandName 'Set-ServiceProperty' -MockWith { } Mock -CommandName 'Start-ServiceWithTimeout' -MockWith { } Mock -CommandName 'Stop-ServiceWithTimeout' -MockWith { } Context 'When both BuiltInAccount, Credential or GroupManagedServiceAccount specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName BuiltInAccount = 'LocalSystem' Credential = $script:testCredential1 } It 'Should throw an error for BuiltInAccount and Credential conflict' { $expectedErrorMessage = $script:localizedData.CredentialParametersAreMutallyExclusive -f $setTargetResourceParameters.Name { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $expectedErrorMessage } $setTargetResourceParameters = @{ Name = $script:testServiceName BuiltInAccount = 'LocalSystem' GroupManagedServiceAccount = $script:gMSAUser1 } It 'Should throw an error for BuiltInAccount and GroupManagedServiceAccount conflict' { $expectedErrorMessage = $script:localizedData.CredentialParametersAreMutallyExclusive -f $setTargetResourceParameters.Name { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $expectedErrorMessage } $setTargetResourceParameters = @{ Name = $script:testServiceName GroupManagedServiceAccount = $script:gMSAUser1 Credential = $script:testCredential1 } It 'Should throw an error for Credential and GroupManagedServiceAccount conflict' { $expectedErrorMessage = $script:localizedData.CredentialParametersAreMutallyExclusive -f $setTargetResourceParameters.Name { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $expectedErrorMessage } } Context 'When a service does not exist and Ensure set to Absent' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Absent' } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service properties' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -Times 0 -Scope 'Context' } It 'Should not attempt to start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to stop or restart the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -Times 0 -Scope 'Context' } } Context 'When a service does not exist, Ensure set to Present, and Path not specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' } It 'Should throw an error for the missing path' { $expectedErrorMessage = $script:localizedData.ServiceDoesNotExistPathMissingError -f $script:testServiceName { Set-TargetResource @setTargetResourceParameters } | Should -Throw -ExpectedMessage $expectedErrorMessage } } Context 'When a service does not exist, Ensure set to Present, and Path specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'FakePath' } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should create the service' { Assert-MockCalled -CommandName 'New-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name -and $BinaryPathName -eq $setTargetResourceParameters.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service properties' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -Times 0 -Scope 'Context' } It 'Should start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to stop or restart the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -Times 0 -Scope 'Context' } } Context 'When a service does not exist, Ensure set to Present, State set to Running, and all parameters except Credential and GroupManagedServiceAccount specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'FakePath' StartupType = 'Automatic' BuiltInAccount = 'LocalSystem' DesktopInteract = $true State = 'Running' DisplayName = 'TestDisplayName' Description = 'Test device description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $State -eq $setTargetResourceParameters.State } -Times 1 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should create the service' { Assert-MockCalled -CommandName 'New-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name -and $BinaryPathName -eq $setTargetResourceParameters.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should change all service properties except Credential' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $BuiltInAccount -eq $setTargetResourceParameters.BuiltInAccount -and $DesktopInteract -eq $setTargetResourceParameters.DesktopInteract -and $DisplayName -eq $setTargetResourceParameters.DisplayName -and $Description -eq $setTargetResourceParameters.Description -and $null -eq (Compare-Object -ReferenceObject $setTargetResourceParameters.Dependencies -DifferenceObject $Dependencies) } -Times 1 -Scope 'Context' } It 'Should start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to stop or restart the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -Times 0 -Scope 'Context' } } Context 'When a service does not exist, Ensure set to Present, State set to Stopped, and all parameters except BuiltInAccount and GroupManagedServiceAccount specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'FakePath' StartupType = 'Disabled' Credential = $script:testCredential1 DesktopInteract = $true State = 'Stopped' DisplayName = 'TestDisplayName' Description = 'Test device description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $State -eq $setTargetResourceParameters.State } -Times 1 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should create the service' { Assert-MockCalled -CommandName 'New-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name -and $BinaryPathName -eq $setTargetResourceParameters.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should change all service properties except BuiltInAccount' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $null -eq (Compare-Object -ReferenceObject $setTargetResourceParameters.Credential -DifferenceObject $Credential) -and $DesktopInteract -eq $setTargetResourceParameters.DesktopInteract -and $DisplayName -eq $setTargetResourceParameters.DisplayName -and $Description -eq $setTargetResourceParameters.Description -and $null -eq (Compare-Object -ReferenceObject $setTargetResourceParameters.Dependencies -DifferenceObject $Dependencies) } -Times 1 -Scope 'Context' } It 'Should not attempt to start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should stop the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } } Context 'When a service does not exist, Ensure set to Present, State set to Stopped, and all parameters except BuiltInAccount and Credential specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'FakePath' StartupType = 'Disabled' GroupManagedServiceAccount = $script:gMSAUser1 DesktopInteract = $true State = 'Stopped' DisplayName = 'TestDisplayName' Description = 'Test device description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $State -eq $setTargetResourceParameters.State } -Times 1 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should create the service' { Assert-MockCalled -CommandName 'New-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name -and $BinaryPathName -eq $setTargetResourceParameters.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should set the service to start with the GroupManagedServiceAccount' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $GroupManagedServiceAccount -eq $setTargetResourceParameters.GroupManagedServiceAccount } -Times 1 -Scope 'Context' } It 'Should not attempt to start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should stop the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } } $testService = @{ Name = 'TestServiceName' DisplayName = 'TestDisplayName' Status = 'TestServiceStatus' StartType = 'TestServiceStartType' ServicesDependedOn = @( @{ Name = 'TestServiceDependency1' }, @{ Name = 'TestServiceDependency2' } ) } Mock -CommandName 'Get-Service' -MockWith { return $testService } Context 'When a service exists and Ensure set to Absent' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Absent' } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should stop the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service properties' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -Times 0 -Scope 'Context' } It 'Should not attempt to start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -Times 0 -Scope 'Context' } } Context 'When a service exists and Ensure set to Present' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service properties' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -Times 0 -Scope 'Context' } It 'Should start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to stop or restart the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -Times 0 -Scope 'Context' } } Context 'When a service exists, Ensure set to Present, and Path specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'TestPath' } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $Path -eq $setTargetResourceParameters.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to change the service properties' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -Times 0 -Scope 'Context' } It 'Should stop the service to restart it' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } } Context 'When a service exists, Ensure set to Present, State set to Stopped, and all parameters except Credential and GroupManagedServiceAccount specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'FakePath' StartupType = 'Automatic' BuiltInAccount = 'LocalSystem' DesktopInteract = $true State = 'Stopped' DisplayName = 'TestDisplayName' Description = 'Test device description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $State -eq $setTargetResourceParameters.State } -Times 1 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $Path -eq $setTargetResourceParameters.Path } -Times 1 -Scope 'Context' } It 'Should change all service properties except Credential' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $BuiltInAccount -eq $setTargetResourceParameters.BuiltInAccount -and $DesktopInteract -eq $setTargetResourceParameters.DesktopInteract -and $DisplayName -eq $setTargetResourceParameters.DisplayName -and $Description -eq $setTargetResourceParameters.Description -and $null -eq (Compare-Object -ReferenceObject $setTargetResourceParameters.Dependencies -DifferenceObject $Dependencies) } -Times 1 -Scope 'Context' } It 'Should not attempt to start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should stop the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } } Context 'When a service exists, Ensure set to Present, State set to Ignore, and all parameters except Path, BuiltInAccount and GroupManagedServiceAccount specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' StartupType = 'Manual' Credential = $script:testCredential1 DesktopInteract = $true State = 'Ignore' DisplayName = 'TestDisplayName' Description = 'Test device description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $State -eq $setTargetResourceParameters.State } -Times 1 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should change all service properties except BuiltInAccount' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $StartupType -eq $setTargetResourceParameters.StartupType -and $BuiltInAccount -eq $setTargetResourceParameters.BuiltInAccount -and $DesktopInteract -eq $setTargetResourceParameters.DesktopInteract -and $DisplayName -eq $setTargetResourceParameters.DisplayName -and $Description -eq $setTargetResourceParameters.Description -and $null -eq (Compare-Object -ReferenceObject $setTargetResourceParameters.Dependencies -DifferenceObject $Dependencies) } -Times 1 -Scope 'Context' } It 'Should not attempt to start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to stop the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -Times 0 -Scope 'Context' } } Context 'When a service exists, Ensure set to Present, and DesktopInteract specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' DesktopInteract = $true } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should not attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -Times 0 -Scope 'Context' } It 'Should change only DesktopInteract service property' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $DesktopInteract -eq $setTargetResourceParameters.DesktopInteract } -Times 1 -Scope 'Context' } It 'Should start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to stop the service' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -Times 0 -Scope 'Context' } } Mock -CommandName 'Set-ServicePath' -MockWith { return $false } Context 'When a service exists, Ensure set to Present, and matching Path to service path specified' { $setTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'TestPath' } It 'Should not throw' { { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to create the service' { Assert-MockCalled -CommandName 'New-Service' -Times 0 -Scope 'Context' } It 'Should not attempt to remove the service' { Assert-MockCalled -CommandName 'Remove-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should attempt to change the service path' { Assert-MockCalled -CommandName 'Set-ServicePath' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name -and $Path -eq $setTargetResourceParameters.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to change the service properties' { Assert-MockCalled -CommandName 'Set-ServiceProperty' -Times 0 -Scope 'Context' } It 'Should not stop the service to restart it' { Assert-MockCalled -CommandName 'Stop-ServiceWithTimeout' -Times 0 -Scope 'Context' } It 'Should start the service' { Assert-MockCalled -CommandName 'Start-ServiceWithTimeout' -ParameterFilter { $ServiceName -eq $setTargetResourceParameters.Name } -Times 1 -Scope 'Context' } } } Describe 'xService\Test-TargetResource' { Mock -CommandName 'Assert-NoStartupTypeStateConflict' -MockWith { } Mock -CommandName 'Get-TargetResource' -MockWith { return @{ Name = $script:testServiceName Ensure = 'Absent' } } Mock -CommandName 'Test-PathsMatch' -MockWith { return $true } Mock -CommandName 'ConvertTo-StartName' -MockWith { return $Username } Context 'When multiple of BuiltInAccount, Credential or GroupManagedServiceAccount are specified' { $testTargetResourceParameters = @{ Name = $script:testServiceName BuiltInAccount = 'LocalSystem' Credential = $script:testCredential1 } It 'Should throw an error for BuiltInAccount and Credential conflict' { $expectedErrorMessage = $script:localizedData.CredentialParametersAreMutallyExclusive -f $testTargetResourceParameters.Name { Test-TargetResource @testTargetResourceParameters } | Should -Throw -ExpectedMessage $expectedErrorMessage } $testTargetResourceParameters = @{ Name = $script:testServiceName BuiltInAccount = 'LocalSystem' GroupManagedServiceAccount = $script:gMSAUser1 } It 'Should throw an error for BuiltInAccount and GroupManagedServiceAccount conflict' { $expectedErrorMessage = $script:localizedData.CredentialParametersAreMutallyExclusive -f $testTargetResourceParameters.Name { Test-TargetResource @testTargetResourceParameters } | Should -Throw -ExpectedMessage $expectedErrorMessage } $testTargetResourceParameters = @{ Name = $script:testServiceName GroupManagedServiceAccount = $script:gMSAUser1 Credential = $script:testCredential1 } It 'Should throw an error for Credential and GroupManagedServiceAccount conflict' { $expectedErrorMessage = $script:localizedData.CredentialParametersAreMutallyExclusive -f $testTargetResourceParameters.Name { Test-TargetResource @testTargetResourceParameters } | Should -Throw -ExpectedMessage $expectedErrorMessage } } Context 'When a service does not exist and Ensure set to Absent' { $testTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Absent' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } Context 'When a service does not exist and Ensure set to Present' { $testTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } Context 'When a service does not exist, Ensure set to Present, and StartupType is set to Disabled' { $testTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' StartupType = 'Disabled' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $testTargetResourceParameters.Name -and $StartupType -eq $testTargetResourceParameters.StartupType } -Times 1 -Scope 'Context' } It 'Should attempt retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } $serviceResourceWithAllProperties = @{ Name = $script:testServiceName Ensure = 'Present' StartupType = 'Automatic' BuiltInAccount = 'LocalSystem' DesktopInteract = $false State = 'Running' Path = 'TestPath' DisplayName = 'TestDisplayName' Description = 'Test service description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } Mock -CommandName 'Get-TargetResource' -MockWith { return $serviceResourceWithAllProperties } Context 'When a service exists and Ensure set to Absent' { $testTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Absent' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } Context 'When a service exists and Ensure set to Present' { $testTargetResourceParameters = @{ Name = $script:testServiceName Ensure = 'Present' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not attempt to test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } Context 'When a service exists, Ensure set to Present, and all matching parameters specified except Credential and GroupManagedServiceAccount' { $testTargetResourceParameters = $serviceResourceWithAllProperties It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $testTargetResourceParameters.Name -and $StartupType -eq $testTargetResourceParameters.StartupType -and $State -eq $testTargetResourceParameters.State } -Times 1 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -ParameterFilter { $ExpectedPath -eq $testTargetResourceParameters.Path -and $ActualPath -eq $serviceResourceWithAllProperties.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } $mismatchingParameters = @{ StartupType = 'Manual' BuiltInAccount = 'NetworkService' DesktopInteract = $true State = 'Stopped' DisplayName = 'MismatchingDisplayName' Description = 'Mismatching service description' Dependencies = @( 'TestServiceDependency3', 'TestServiceDependency4' ) } foreach ($mismatchingParameterName in $mismatchingParameters.Keys) { Context "Service exists, Ensure set to Present, and mismatching $mismatchingParameterName specified" { $testTargetResourceParameters = @{ Name = $serviceResourceWithAllProperties.Name Ensure = 'Present' $mismatchingParameterName = $mismatchingParameters[$mismatchingParameterName] } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } if ($mismatchingParameterName -eq 'StartupType') { It 'Should check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -ParameterFilter { $ServiceName -eq $testTargetResourceParameters.Name -and $StartupType -eq $testTargetResourceParameters.StartupType -and $State -eq 'Running' } -Times 1 -Scope 'Context' } } else { It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } } Context 'When a service exists, Ensure set to Present, and State is set to Ignore' { $testTargetResourceParameters = @{ Name = $serviceResourceWithAllProperties.Name Ensure = 'Present' State = 'Ignore' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } $serviceResourceWithCustomBuiltInAccount = @{ Name = $script:testServiceName Ensure = 'Present' State = 'Running' BuiltInAccount = $script:testCredential1.UserName DisplayName = 'TestDisplayName' Description = 'Test service description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } Mock -CommandName 'Get-TargetResource' -MockWith { return $serviceResourceWithCustomBuiltInAccount } Context 'When a service exists, Ensure set to Present, and matching Credential specified' { $testTargetResourceParameters = @{ Name = $serviceResourceWithCustomBuiltInAccount.Name Ensure = 'Present' Credential = $script:testCredential1 } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should convert the credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $script:testCredential1.UserName } -Times 1 -Scope 'Context' } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } Context 'When a service exists, Ensure set to Present, and mismatching Credential specified' { $testTargetResourceParameters = @{ Name = $serviceResourceWithCustomBuiltInAccount.Name Ensure = 'Present' Credential = $script:testCredential2 } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should convert the credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $script:testCredential2.UserName } -Times 1 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } $allowedEmptyPropertyNames = @( 'DisplayName', 'Description', 'Dependencies' ) foreach ($allowedEmptyPropertyName in $allowedEmptyPropertyNames) { Context "Service exists, Ensure set to Present, $allowedEmptyPropertyName specified as empty" { $testTargetResourceParameters = @{ Name = $serviceResourceWithCustomBuiltInAccount.Name Ensure = 'Present' } if ($allowedEmptyPropertyName -eq 'Dependencies') { $testTargetResourceParameters[$allowedEmptyPropertyName] = @() } else { $testTargetResourceParameters[$allowedEmptyPropertyName] = '' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } } $serviceResourceWithNullProperties = @{ Name = $script:testServiceName Ensure = 'Present' Path = 'TestPath' State = 'Running' } foreach ($nullPropertyName in $allowedEmptyPropertyNames) { $serviceResourceWithNullProperties[$nullPropertyName] = $null } Mock -CommandName 'Get-TargetResource' -MockWith { return $serviceResourceWithNullProperties } foreach ($nullPropertyName in $allowedEmptyPropertyNames) { Context "Service exists but DisplayName, Description, and Dependencies are null, Ensure set to Present, $nullPropertyName specified" { $testTargetResourceParameters = @{ Name = $serviceResourceWithNullProperties.Name Ensure = 'Present' $nullPropertyName = 'Something' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } Context "Service exists but DisplayName, Description, and Dependencies are null, Ensure set to Present, $nullPropertyName not specified" { $testTargetResourceParameters = @{ Name = $serviceResourceWithNullProperties.Name Ensure = 'Present' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } } Mock -CommandName 'Test-PathsMatch' -MockWith { return $false } Context 'When a service exists, Ensure set to Present, and mismatching Path specified' { $testTargetResourceParameters = @{ Name = $serviceResourceWithCustomBuiltInAccount.Name Ensure = 'Present' Path = 'Mismatching path' } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -ParameterFilter { $ExpectedPath -eq $testTargetResourceParameters.Path -and $ActualPath -eq $serviceResourceWithNullProperties.Path } -Times 1 -Scope 'Context' } It 'Should not attempt to convert a credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } $serviceResourceWithGroupManagedServiceAccount = @{ Name = $script:testServiceName Ensure = 'Present' State = 'Running' BuiltInAccount = $script:gMSAUser1 DisplayName = 'TestDisplayName' Description = 'Test service description' Dependencies = @( 'TestServiceDependency1', 'TestServiceDependency2' ) } Mock -CommandName 'Get-TargetResource' -MockWith { return $serviceResourceWithGroupManagedServiceAccount } Context 'When a service exists, Ensure set to Present, and mismatching GroupManagedServiceAccount specified' { $testTargetResourceParameters = @{ Name = $serviceResourceWithCustomBuiltInAccount.Name Ensure = 'Present' GroupManagedServiceAccount = $script:gMSAUser2 } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should not check for a startup type and state conflict' { Assert-MockCalled -CommandName 'Assert-NoStartupTypeStateConflict' -Times 0 -Scope 'Context' } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-TargetResource' -ParameterFilter { $Name -eq $testTargetResourceParameters.Name } -Times 1 -Scope 'Context' } It 'Should not test if the service path matches the specified path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -Times 0 -Scope 'Context' } It 'Should convert the credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $script:gMSAUser2 } -Times 1 -Scope 'Context' } It 'Should return false' { Test-TargetResource @testTargetResourceParameters | Should -Be $false } } Context 'When a service exists, Ensure set to Present, and matching GroupManagedServiceAccount specified' { $testTargetResourceParameters = @{ Name = $serviceResourceWithCustomBuiltInAccount.Name Ensure = 'Present' GroupManagedServiceAccount = $script:gMSAUser1 } It 'Should not throw' { { Test-TargetResource @testTargetResourceParameters } | Should -Not -Throw } It 'Should return true' { Test-TargetResource @testTargetResourceParameters | Should -Be $true } } } Describe 'xService\Get-ServiceCimInstance' { Mock -CommandName 'Get-CimInstance' -MockWith { } Context 'When a service does not exist' { It 'Should not throw' { { Get-ServiceCimInstance -ServiceName $script:testServiceName } | Should -Not -Throw } It 'Should retrieve the CIM instance of the service with the given name' { Assert-MockCalled -CommandName 'Get-CimInstance' -ParameterFilter {$ClassName -ieq 'Win32_Service' -and $Filter.Contains($script:testServiceName)} -Times 1 -Scope 'Context' } It 'Should return null' { Get-ServiceCimInstance -ServiceName $script:testServiceName | Should -Be $null } } $testCimInstance = 'TestCimInstance' Mock -CommandName 'Get-CimInstance' -MockWith { return $testCimInstance } Context 'When a service exists' { It 'Should not throw' { { Get-ServiceCimInstance -ServiceName $script:testServiceName } | Should -Not -Throw } It 'Should retrieve the CIM instance of the service with the given name' { Assert-MockCalled -CommandName 'Get-CimInstance' -ParameterFilter {$ClassName -ieq 'Win32_Service' -and $Filter.Contains($script:testServiceName)} -Times 1 -Scope 'Context' } It 'Should return the retrieved CIM instance' { Get-ServiceCimInstance -ServiceName $script:testServiceName | Should -Be $testCimInstance } } } Describe 'xService\ConvertTo-StartupTypeString' { Context 'When StartupType is specifed as Auto' { It 'Should return Automatic' { ConvertTo-StartupTypeString -StartMode 'Auto' | Should -Be 'Automatic' } } Context 'When StartupType is specifed as Manual' { It 'Should return Manual' { ConvertTo-StartupTypeString -StartMode 'Manual' | Should -Be 'Manual' } } Context 'When StartupType is specifed as Disabled' { It 'Should return Disabled' { ConvertTo-StartupTypeString -StartMode 'Disabled' | Should -Be 'Disabled' } } } Describe 'xService\Assert-NoStartupTypeStateConflict' { $stateValues = @( 'Running', 'Stopped', 'Ignore' ) $startupTypeValues = @( 'Manual', 'Automatic', 'Disabled' ) foreach ($startupTypeValue in $startupTypeValues) { foreach ($stateValue in $stateValues) { Context "StartupType specified as $startupTypeValue and State specified as $stateValue" { $assertNoStartupTypeStateConflictParameters = @{ ServiceName = $script:testServiceName StartupType = $startupTypeValue State = $stateValue } if ($stateValue -eq 'Running' -and $startupTypeValue -eq 'Disabled') { It 'Should throw error for conflicting state and startup type' { $errorMessage = $script:localizedData.StartupTypeStateConflict -f $assertNoStartupTypeStateConflictParameters.ServiceName, $startupTypeValue, $stateValue { Assert-NoStartupTypeStateConflict @assertNoStartupTypeStateConflictParameters } | Should -Throw -ExpectedMessage $errorMessage } } elseif ($stateValue -eq 'Stopped' -and $startupTypeValue -eq 'Automatic') { It 'Should throw error for conflicting state and startup type' { $errorMessage = $script:localizedData.StartupTypeStateConflict -f $assertNoStartupTypeStateConflictParameters.ServiceName, $startupTypeValue, $stateValue { Assert-NoStartupTypeStateConflict @assertNoStartupTypeStateConflictParameters } | Should -Throw -ExpectedMessage $errorMessage } } else { It 'Should not throw' { { Assert-NoStartupTypeStateConflict @assertNoStartupTypeStateConflictParameters } | Should -Not -Throw } } } } } } Describe 'xService\Test-PathsMatch' { Context 'When Specified paths match' { It 'Should return true' { $matchingPath = 'MatchingPath' Test-PathsMatch -ExpectedPath $matchingPath -ActualPath $matchingPath | Should -Be $true } } Context 'When Specified paths do not match' { It 'Should return false' { Test-PathsMatch -ExpectedPath 'Path1' -ActualPath 'Path2' | Should -Be $false } } } Describe 'xService\ConvertTo-StartName' { Context 'When Username is specified as LocalSystem' { It 'Should return .\LocalSystem' { ConvertTo-StartName -Username 'LocalSystem' | Should -Be '.\LocalSystem' } } Context 'When Username is specified as LocalService' { It 'Should return NT Authority\LocalService' { ConvertTo-StartName -Username 'LocalService' | Should -Be 'NT Authority\LocalService' } } Context 'When Username is specified as NetworkService' { It 'Should return NT Authority\NetworkService' { ConvertTo-StartName -Username 'NetworkService' | Should -Be 'NT Authority\NetworkService' } } Context 'When custom username is specified without any \ or @ characters' { It 'Should return custom username prefixed with .\' { $customUsername = 'TestUsername' ConvertTo-StartName -Username $customUsername | Should -Be ".\$customUsername" } } Context 'When custom username is specified that starts with the local computer name followed by a \ character' { It 'Should return custom username prefixed with .\ instead of the local computer name' { $customUsername = 'TestUsername' $customUsernameWithComputerNamePrefix = "$env:computerName\$customUsername" ConvertTo-StartName -Username $customUsernameWithComputerNamePrefix | Should -Be ".\$customUsername" } } Context 'When custom username is specified with a \ character and a custom domain' { It 'Should return the custom username with no changes' { $customUsername = 'TestDomain\TestUsername' ConvertTo-StartName -Username $customUsername | Should -Be $customUsername } } Context 'When custom username is specified with an @ character' { It 'Should return the custom username with no changes' { $customUsername = 'TestUsername@TestDomain' ConvertTo-StartName -Username $customUsername | Should -Be $customUsername } } } Describe 'xService\Set-ServicePath' { $testServiceCimInstance = New-CimInstance -ClassName 'Win32_Service' -Property @{ PathName = 'TestPath' } -ClientOnly try { Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'Test-PathsMatch' -MockWith { return $true } $invokeCimMethodSuccessResult = @{ ReturnValue = 0 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodSuccessResult } Context 'When specified path matches the service path' { $setServicePathParameters = @{ ServiceName = $script:testServiceName Path = $testServiceCimInstance.PathName } It 'Should not throw' { { Set-ServicePath @setServicePathParameters } | Should -Not -Throw } It 'Should retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePathParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should test if the specfied path matches the service path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -ParameterFilter { $ExpectedPath -eq $setServicePathParameters.Path -and $ActualPath -eq $testServiceCimInstance.PathName } -Times 1 -Scope 'Context' } It 'Should not change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } It 'Should return false' { Set-ServicePath @setServicePathParameters | Should -Be $false } } Mock -CommandName 'Test-PathsMatch' -MockWith { return $false } Context 'When specified path does not match the service path and the path change succeeds' { $setServicePathParameters = @{ ServiceName = $script:testServiceName Path = 'NewTestPath' } It 'Should not throw' { { Set-ServicePath @setServicePathParameters } | Should -Not -Throw } It 'Should retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePathParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should test if the specfied path matches the service path' { Assert-MockCalled -CommandName 'Test-PathsMatch' -ParameterFilter { $ExpectedPath -eq $setServicePathParameters.Path -and $ActualPath -eq $testServiceCimInstance.PathName } -Times 1 -Scope 'Context' } It 'Should change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $Arguments.PathName -eq $setServicePathParameters.Path} -Times 1 -Scope 'Context' } It 'Should return true' { Set-ServicePath @setServicePathParameters | Should -Be $true } } $invokeCimMethodFailResult = @{ ReturnValue = 1 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodFailResult } Context 'When specified path does not match the service path and the path change fails' { $setServicePathParameters = @{ ServiceName = $script:testServiceName Path = 'NewTestPath' } It 'Should throw error for failed service path change' { $errorMessage = $script:localizedData.InvokeCimMethodFailed -f 'Change', $setServicePathParameters.ServiceName, 'PathName', $invokeCimMethodFailResult.ReturnValue { Set-ServicePath @setServicePathParameters } | Should -Throw -ExpectedMessage $errorMessage } } } finally { $testServiceCimInstance.Dispose() # Release the reference so the garbage collector can clean up $testServiceCimInstance = $null } } Describe 'xService\Set-ServiceDependency' { $testServiceCimInstance = New-CimInstance -ClassName 'Win32_Service' -ClientOnly try { $testService = @{ ServicesDependedOn = @( @{ Name = 'TestDependency1' }, @{ Name = 'TestDependency2'} ) } Mock -CommandName 'Get-Service' -MockWith { return $testService } Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } $invokeCimMethodSuccessResult = @{ ReturnValue = 0 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodSuccessResult } Context 'When specified dependencies match the service dependencies' { $setServiceDependenciesParameters = @{ ServiceName = $script:testServiceName Dependencies = $testService.ServicesDependedOn.Name } It 'Should not throw' { { Set-ServiceDependency @setServiceDependenciesParameters } | Should -Not -Throw } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -Times 0 -Scope 'Context' } It 'Should not change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When specified dependencies do not match the populated service dependencies and the dependency change succeeds' { $setServiceDependenciesParameters = @{ ServiceName = $script:testServiceName Dependencies = @( 'TestDependency3', 'TestDependency4' ) } It 'Should not throw' { { Set-ServiceDependency @setServiceDependenciesParameters } | Should -Not -Throw } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $null -eq (Compare-Object -ReferenceObject $setServiceDependenciesParameters.Dependencies -DifferenceObject $Arguments.ServiceDependencies) } -Times 1 -Scope 'Context' } } Context 'When specified empty dependencies do not match the populated service dependencies and the dependency change succeeds' { $setServiceDependenciesParameters = @{ ServiceName = $script:testServiceName Dependencies = @() } It 'Should not throw' { { Set-ServiceDependency @setServiceDependenciesParameters } | Should -Not -Throw } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $null -eq (Compare-Object -ReferenceObject $setServiceDependenciesParameters.Dependencies -DifferenceObject $Arguments.ServiceDependencies) } -Times 1 -Scope 'Context' } } $testServiceWithNoDependencies = @{ ServicesDependedOn = $null } Mock -CommandName 'Get-Service' -MockWith { return $testServiceWithNoDependencies } Context 'When specified empty dependencies match the null service dependencies' { $setServiceDependenciesParameters = @{ ServiceName = $script:testServiceName Dependencies = @() } It 'Should not throw' { { Set-ServiceDependency @setServiceDependenciesParameters } | Should -Not -Throw } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -Times 0 -Scope 'Context' } It 'Should not change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When specified dependencies do not match the null service dependencies and the dependency change succeeds' { $setServiceDependenciesParameters = @{ ServiceName = $script:testServiceName Dependencies = @( 'TestDependency3', 'TestDependency4' ) } It 'Should not throw' { { Set-ServiceDependency @setServiceDependenciesParameters } | Should -Not -Throw } It 'Should retrieve the service' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceDependenciesParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $null -eq (Compare-Object -ReferenceObject $setServiceDependenciesParameters.Dependencies -DifferenceObject $Arguments.ServiceDependencies) } -Times 1 -Scope 'Context' } } $invokeCimMethodFailResult = @{ ReturnValue = 1 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodFailResult } Context 'When specified dependencies do not match the service dependencies and the dependency change fails' { $setServiceDependenciesParameters = @{ ServiceName = $script:testServiceName Dependencies = @( 'TestDependency3', 'TestDependency4' ) } It 'Should throw error for failed service path change' { $errorMessage = $script:localizedData.InvokeCimMethodFailed -f 'Change', $setServiceDependenciesParameters.ServiceName, 'ServiceDependencies', $invokeCimMethodFailResult.ReturnValue { Set-ServiceDependency @setServiceDependenciesParameters } | Should -Throw -ExpectedMessage $errorMessage } } } finally { $testServiceCimInstance.Dispose() } } Describe 'xService\Set-ServiceAccountProperty' { $testServiceCimInstance = New-CimInstance -ClassName 'Win32_Service' -Property @{ StartName = 'LocalSystem'; DesktopInteract = $true } -ClientOnly try { Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'Grant-LogOnAsServiceRight' -MockWith { } Mock -CommandName 'ConvertTo-StartName' -MockWith { return $Username } $invokeCimMethodSuccessResult = @{ ReturnValue = 0 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodSuccessResult } Context 'When no parameters are specified' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not attempt to convert the built-in account or credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should not attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 0 -Scope 'Context' } It 'Should not attempt to change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When matching DesktopInteract specified' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName DesktopInteract = $testServiceCimInstance.DesktopInteract } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not attempt to convert the built-in account or credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should not attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 0 -Scope 'Context' } It 'Should not attempt to change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When mismatching DesktopInteract specified and service change succeeds' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName DesktopInteract = -not $testServiceCimInstance.DesktopInteract } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not attempt to convert the built-in account or credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -Times 0 -Scope 'Context' } It 'Should not attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 0 -Scope 'Context' } It 'Should change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $Arguments.DesktopInteract -eq $setServiceAccountPropertyParameters.DesktopInteract} -Times 1 -Scope 'Context' } } Context 'When credential with matching username specified' { $secureTestPassword = ConvertTo-SecureString -String 'TestPassword' -AsPlainText -Force $testCredentialWithMatchingUsername = New-Object -TypeName 'PSCredential' -ArgumentList @( $testServiceCimInstance.StartName, $secureTestPassword ) $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName Credential = $testCredentialWithMatchingUsername } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should convert the credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $setServiceAccountPropertyParameters.Credential.UserName } -Times 1 -Scope 'Context' } It 'Should not attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 0 -Scope 'Context' } It 'Should not attempt to change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When credential with mismatching username specified and service change succeeds' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName Credential = $script:testCredential1 } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should convert the credential username to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $setServiceAccountPropertyParameters.Credential.UserName } -Times 1 -Scope 'Context' } It 'Should grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -ParameterFilter { $Username -eq $setServiceAccountPropertyParameters.Credential.UserName } -Times 1 -Scope 'Context' } It 'Should change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $Arguments.StartName -eq $setServiceAccountPropertyParameters.Credential.UserName -and $Arguments.StartPassword -eq $setServiceAccountPropertyParameters.Credential.GetNetworkCredential().Password } -Times 1 -Scope 'Context' } } Context 'When matching BuiltInAccount specified' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName BuiltInAccount = $testServiceCimInstance.StartName } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should convert the built-in account to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $setServiceAccountPropertyParameters.BuiltInAccount } -Times 1 -Scope 'Context' } It 'Should not attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 0 -Scope 'Context' } It 'Should not attempt to change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When mismatching BuiltInAccount specified and service change succeeds' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName BuiltInAccount = 'NetworkService' } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should convert the built-in account to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $setServiceAccountPropertyParameters.BuiltInAccount } -Times 1 -Scope 'Context' } It 'Should not attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 0 -Scope 'Context' } It 'Should change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $Arguments.StartName -eq $setServiceAccountPropertyParameters.BuiltInAccount -and $Arguments.StartPassword -eq [System.String]::Empty } -Times 1 -Scope 'Context' } } $testServiceCimInstance = New-CimInstance -ClassName 'Win32_Service' -Property @{ StartName = $script:gMSAUser1; DesktopInteract = $true } -ClientOnly Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Context 'When matching GroupManagedServiceAccount specified' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName GroupManagedServiceAccount = $script:gMSAUser1 } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should convert the GroupManagedServiceAccount to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $setServiceAccountPropertyParameters.GroupManagedServiceAccount } -Times 1 -Scope 'Context' } It 'Should not attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 0 -Scope 'Context' } It 'Should not attempt to change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When mismatching GroupManagedServiceAccount specified and service change succeeds' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName GroupManagedServiceAccount = $script:gMSAUser2 } It 'Should not throw' { { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceAccountPropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should convert the GroupManagedServiceAccount to a service start name' { Assert-MockCalled -CommandName 'ConvertTo-StartName' -ParameterFilter { $Username -eq $setServiceAccountPropertyParameters.GroupManagedServiceAccount } -Times 1 -Scope 'Context' } It 'Should attempt to grant Log on As a Service right' { Assert-MockCalled -CommandName 'Grant-LogOnAsServiceRight' -Times 1 -Scope 'Context' } It 'Should change service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $Arguments.StartName -eq $setServiceAccountPropertyParameters.GroupManagedServiceAccount } -Times 1 -Scope 'Context' } } $invokeCimMethodFailResult = @{ ReturnValue = 1 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodFailResult } Context 'When mismatching DesktopInteract specified and service change fails' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName DesktopInteract = -not $testServiceCimInstance.DesktopInteract } It 'Should throw an error for service change failure' { $errorMessage = $script:localizedData.InvokeCimMethodFailed -f 'Change', $setServiceAccountPropertyParameters.ServiceName, 'DesktopInteract', $invokeCimMethodFailResult.ReturnValue { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Throw -ExpectedMessage $errorMessage } } Context 'When Credential with mismatching username specified and service change fails' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName Credential = $script:testCredential1 } It 'Should throw an error for service change failure' { $errorMessage = $script:localizedData.InvokeCimMethodFailed -f 'Change', $setServiceAccountPropertyParameters.ServiceName, 'StartName, StartPassword', $invokeCimMethodFailResult.ReturnValue { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Throw -ExpectedMessage $errorMessage } } Context 'When mismatching BuiltInAccount specified and service change fails' { $setServiceAccountPropertyParameters = @{ ServiceName = $script:testServiceName BuiltInAccount = 'NetworkService' } It 'Should throw an error for service change failure' { $errorMessage = $script:localizedData.InvokeCimMethodFailed -f 'Change', $setServiceAccountPropertyParameters.ServiceName, 'StartName, StartPassword', $invokeCimMethodFailResult.ReturnValue { Set-ServiceAccountProperty @setServiceAccountPropertyParameters } | Should -Throw -ExpectedMessage $errorMessage } } } finally { $testServiceCimInstance.Dispose() # Release the reference so the garbage collector can clean up $testServiceCimInstance = $null } } Describe 'xService\Set-ServiceStartupType' { $testServiceCimInstance = New-CimInstance -ClassName 'Win32_Service' -Property @{ StartMode = 'Manual' } -ClientOnly try { Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'ConvertTo-StartupTypeString' -MockWith { return $testServiceCimInstance.StartMode } $invokeCimMethodSuccessResult = @{ ReturnValue = 0 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodSuccessResult } Context 'When specified startup type matches the service startup type' { $setServiceStartupTypeParameters = @{ ServiceName = $script:testServiceName StartupType = $testServiceCimInstance.StartMode } It 'Should not throw' { { Set-ServiceStartupType @setServiceStartupTypeParameters } | Should -Not -Throw } It 'Should retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceStartupTypeParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not attempt to change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -Times 0 -Scope 'Context' } } Context 'When specified startup type does not match the service startup type and service change succeeds' { $setServiceStartupTypeParameters = @{ ServiceName = $script:testServiceName StartupType = 'Automatic' } It 'Should not throw' { { Set-ServiceStartupType @setServiceStartupTypeParameters } | Should -Not -Throw } It 'Should retrieve the service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServiceStartupTypeParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should change the service' { Assert-MockCalled -CommandName 'Invoke-CimMethod' -ParameterFilter { $InputObject -eq $testServiceCimInstance -and $MethodName -eq 'Change' -and $Arguments.StartMode -eq $setServiceStartupTypeParameters.StartupType } -Times 1 -Scope 'Context' } } $invokeCimMethodFailResult = @{ ReturnValue = 1 } Mock -CommandName 'Invoke-CimMethod' -MockWith { return $invokeCimMethodFailResult } Context 'When specified startup type does not match the service startup type and service change fails' { $setServiceStartupTypeParameters = @{ ServiceName = $script:testServiceName StartupType = 'Automatic' } It 'Should throw error for failed service change' { $errorMessage = $script:localizedData.InvokeCimMethodFailed -f 'Change', $setServiceStartupTypeParameters.ServiceName, 'StartMode', $invokeCimMethodFailResult.ReturnValue { Set-ServiceStartupType @setServiceStartupTypeParameters } | Should -Throw -ExpectedMessage $errorMessage } } } finally { $testServiceCimInstance.Dispose() # Release the reference so the garbage collector can clean up $testServiceCimInstance = $null } } Describe 'xService\Set-ServiceProperty' { $testServiceCimInstance = @{ Description = 'Test service description' DisplayName = 'TestDisplayName' } Mock -CommandName 'Get-ServiceCimInstance' -MockWith { return $testServiceCimInstance } Mock -CommandName 'Set-Service' -MockWith { } Mock -CommandName 'Set-ServiceDependency' -MockWith { } Mock -CommandName 'Set-ServiceAccountProperty' -MockWith { } Mock -CommandName 'Set-ServiceStartupType' -MockWith { } Context 'When no parameters are specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should not set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -Times 0 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When mismatching DisplayName is specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName DisplayName = 'NewDisplayName' } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should set service display name' { Assert-MockCalled -CommandName 'Set-Service' -ParameterFilter { $Name -eq $setServicePropertyParameters.ServiceName -and $DisplayName -eq $setServicePropertyParameters.DisplayName } -Times 1 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should not set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -Times 0 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When mismatching Description is specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName Description = 'New service description' } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should set service description' { Assert-MockCalled -CommandName 'Set-Service' -ParameterFilter { $Name -eq $setServicePropertyParameters.ServiceName -and $Description -eq $setServicePropertyParameters.Description } -Times 1 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should not set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -Times 0 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When matching Description and DisplayName specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName DisplayName = $testServiceCimInstance.DisplayName Description = $testServiceCimInstance.Description } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should not set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -Times 0 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When Dependencies specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName Dependencies = @( 'TestDependency1' ) } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled ` -CommandName 'Get-ServiceCimInstance' ` -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } ` -Times 1 ` -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should set service dependencies' { Assert-MockCalled ` -CommandName 'Set-ServiceDependency' ` -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName -and ` $null -eq (Compare-Object -ReferenceObject $setServicePropertyParameters.Dependencies -DifferenceObject $Dependencies) } ` -Times 1 ` -Scope 'Context' } It 'Should not set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -Times 0 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When Credential specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName Credential = $script:testCredential1 } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName -and [System.Management.Automation.PSCredential]::Equals($setServicePropertyParameters.Credential, $Credential) } -Times 1 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When BuiltInAccount specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName BuiltInAccount = 'LocalService' } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName -and $BuiltInAccount -eq $setServicePropertyParameters.BuiltInAccount } -Times 1 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When GroupManagedServiceAccount specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName GroupManagedServiceAccount = $script:gMSAUser1 } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName -and $GroupManagedServiceAccount -eq $setServicePropertyParameters.GroupManagedServiceAccount } -Times 1 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When DesktopInteract specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName DesktopInteract = $true } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName -and $DesktopInteract -eq $setServicePropertyParameters.DesktopInteract } -Times 1 -Scope 'Context' } It 'Should not set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -Times 0 -Scope 'Context' } } Context 'When StartupType specified' { $setServicePropertyParameters = @{ ServiceName = $script:testServiceName StartupType = 'Manual' } It 'Should not throw' { { Set-ServiceProperty @setServicePropertyParameters } | Should -Not -Throw } It 'Should retrieve service CIM instance' { Assert-MockCalled -CommandName 'Get-ServiceCimInstance' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName } -Times 1 -Scope 'Context' } It 'Should not set service description or display name' { Assert-MockCalled -CommandName 'Set-Service' -Times 0 -Scope 'Context' } It 'Should not set service dependencies' { Assert-MockCalled -CommandName 'Set-ServiceDependency' -Times 0 -Scope 'Context' } It 'Should not set service account properties' { Assert-MockCalled -CommandName 'Set-ServiceAccountProperty' -Times 0 -Scope 'Context' } It 'Should set service startup type' { Assert-MockCalled -CommandName 'Set-ServiceStartupType' -ParameterFilter { $ServiceName -eq $setServicePropertyParameters.ServiceName -and $StartupType -eq $setServicePropertyParameters.StartupType } -Times 1 -Scope 'Context' } } } Describe 'xService\Remove-ServiceWithTimeout' { Mock -CommandName 'Remove-Service' -MockWith { } Mock -CommandName 'Get-Service' -MockWith { } Context 'When a service removal succeeds' { $removeServiceWithTimeoutParameters = @{ Name = $script:testServiceName TerminateTimeout = 500 } It 'Should not throw' { { Remove-ServiceWithTimeout @removeServiceWithTimeoutParameters } | Should -Not -Throw } It 'Should remove service' { Assert-MockCalled -CommandName 'Remove-Service' -ParameterFilter { $Name -eq $removeServiceWithTimeoutParameters.Name } -Times 1 -Scope 'Context' } It 'Should retrieve service to check for removal once' { Assert-MockCalled -CommandName 'Get-Service' -ParameterFilter { $Name -eq $removeServiceWithTimeoutParameters.Name } -Times 1 -Scope 'Context' } } Mock -CommandName 'Get-Service' -MockWith { return 'Not null' } Context 'When a service removal fails' { $removeServiceWithTimeoutParameters = @{ Name = $script:testServiceName TerminateTimeout = 500 } It 'Should throw error for service removal timeout' { $errorMessage = $script:localizedData.ServiceDeletionFailed -f $removeServiceWithTimeoutParameters.Name { Remove-ServiceWithTimeout @removeServiceWithTimeoutParameters } | Should -Throw -ExpectedMessage $errorMessage } } } Describe 'xService\Start-ServiceWithTimeout' { Mock -CommandName 'Start-Service' -MockWith { } Mock -CommandName 'Wait-ServiceStateWithTimeout' -MockWith { } $startServiceWithTimeoutParameters = @{ ServiceName = $script:testServiceName StartupTimeout = 500 } $expectedTimeSpan = [System.TimeSpan]::FromMilliseconds($startServiceWithTimeoutParameters.StartupTimeout) It 'Should not throw' { { Start-ServiceWithTimeout @startServiceWithTimeoutParameters } | Should -Not -Throw } It 'Should start service' { Assert-MockCalled -CommandName 'Start-Service' -ParameterFilter { $Name -eq $startServiceWithTimeoutParameters.ServiceName } -Times 1 -Scope 'Describe' } It 'Should wait for service to start' { Assert-MockCalled -CommandName 'Wait-ServiceStateWithTimeout' -ParameterFilter { $ServiceName -eq $startServiceWithTimeoutParameters.ServiceName -and $State -eq [System.ServiceProcess.ServiceControllerStatus]::Running -and [System.TimeSpan]::Equals($expectedTimeSpan, $WaitTimeSpan) } -Times 1 -Scope 'Describe' } } Describe 'xService\Stop-ServiceWithTimeout' { Mock -CommandName 'Stop-Service' -MockWith { } Mock -CommandName 'Wait-ServiceStateWithTimeout' -MockWith { } $stopServiceWithTimeoutParameters = @{ ServiceName = $script:testServiceName TerminateTimeout = 500 } $expectedTimeSpan = [System.TimeSpan]::FromMilliseconds($stopServiceWithTimeoutParameters.TerminateTimeout) It 'Should not throw' { { Stop-ServiceWithTimeout @stopServiceWithTimeoutParameters } | Should -Not -Throw } It 'Should stop service' { Assert-MockCalled -CommandName 'Stop-Service' -ParameterFilter { $Name -eq $stopServiceWithTimeoutParameters.ServiceName } -Times 1 -Scope 'Describe' } It 'Should wait for service to stop' { Assert-MockCalled -CommandName 'Wait-ServiceStateWithTimeout' -ParameterFilter { $ServiceName -eq $stopServiceWithTimeoutParameters.ServiceName -and $State -eq [System.ServiceProcess.ServiceControllerStatus]::Stopped -and [System.TimeSpan]::Equals($expectedTimeSpan, $WaitTimeSpan) } -Times 1 -Scope 'Describe' } } } } finally { Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment } |