Tests/Unit/MSFT_xServiceResource.Tests.ps1

# Need to be able to create a password from plain text for testing purposes
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
param ()

Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) `
                               -ChildPath 'CommonTestHelper.psm1') `
                               -Force

# Need this module to import the localized data
Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) `
              -ChildPath 'DSCResources\CommonResourceHelper.psm1')

# Localized messages for Write-Verbose statements
$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xServiceResource'

$script:testEnvironment = Enter-DscResourceTestEnvironment `
    -DSCResourceModuleName 'xPSDesiredStateConfiguration' `
    -DSCResourceName 'MSFT_xServiceResource' `
    -TestType Unit

# Begin Testing
try
{

    # This is needed so that the ServiceControllerStatus enum is recognized as a valid type
    Add-Type -AssemblyName 'System.ServiceProcess'

    InModuleScope 'MSFT_xServiceResource' {
        $script:DscResourceName = 'MSFT_xServiceResource'

        $script:testServiceName = 'DscTestService'
        $script:testServiceDisplayName = 'DSC test service display name'
        $script:testServiceDescription = 'This is the DSC test service used for unit testing MSFT_xServiceResource'
        $script:testServiceDependsOn = @('winrm','spooler')
        $script:testServiceDependsOnHash = @( @{ name = 'winrm' }, @{ name = 'spooler' } )
        $script:testServiceExecutablePath = Join-Path -Path $ENV:Temp -ChildPath 'DscTestService.exe'
        $script:testServiceStartupType = 'Automatic'
        $script:testServiceStartupTypeWin32 = 'Auto'
        $script:testServiceStatusRunning = [System.ServiceProcess.ServiceControllerStatus]::Running
        $script:testServiceStatusStopped = [System.ServiceProcess.ServiceControllerStatus]::Stopped
        $script:testUsername = 'TestUser'
        $script:testPassword = 'DummyPassword'
        $script:testCredential = New-Object `
            -TypeName System.Management.Automation.PSCredential `
            -ArgumentList ($script:testUsername, `
                          (ConvertTo-SecureString $script:testPassword -AsPlainText -Force))
        $script:testNewUsername = 'DifferentUser'
        $script:testNewCredential = New-Object `
            -TypeName System.Management.Automation.PSCredential `
            -ArgumentList ($script:testNewUsername, `
                          (ConvertTo-SecureString $script:testPassword -AsPlainText -Force))

        $script:testServiceMockRunning = New-Object -TypeName PSObject -Property @{
            Name               = $script:testServiceName
            ServiceName        = $script:testServiceName
            DisplayName        = $script:testServiceDisplayName
            StartType          = $script:testServiceStartupType
            Status             = $script:testServiceStatusRunning
            ServicesDependedOn = $script:testServiceDependsOnHash
        }

        Add-Member -InputObject  $script:testServiceMockRunning `
            -MemberType ScriptMethod `
            -Name Stop -Value { $global:ServiceStopped = $true }

        Add-Member -InputObject  $script:testServiceMockRunning `
            -MemberType ScriptMethod `
            -Name WaitForStatus -Value { param( $Status, $WaitTimeSpan ) }

        $script:testServiceMockStopped = New-Object -TypeName PSObject -Property @{
            Name               = $script:testServiceName
            ServiceName        = $script:testServiceName
            DisplayName        = $script:testServiceDisplayName
            StartType          = $script:testServiceStartupType
            Status             = $script:testServiceStatusStopped
            ServicesDependedOn = $script:testServiceDependsOnHash
        }

        Add-Member -InputObject  $script:testServiceMockStopped `
            -MemberType ScriptMethod `
            -Name Start -Value { $global:ServiceStarted = $true }

        Add-Member -InputObject  $script:testServiceMockStopped `
            -MemberType ScriptMethod `
            -Name WaitForStatus -Value { param( $Status, $WaitTimeSpan ) }

        $script:testWin32ServiceMockRunningLocalSystem = New-Object -TypeName PSObject -Property @{
            Name                    = $script:testServiceName
            Status                  = 'OK'
            DesktopInteract         = $true
            PathName                = $script:testServiceExecutablePath
            StartMode               = $script:testServiceStartupTypeWin32
            Description             = $script:testServiceDescription
            Started                 = $true
            DisplayName             = $script:testServiceDisplayName
            StartName               = 'LocalSystem'
            State                   = $script:testServiceStatusRunning
        }

        $script:splatServiceExistsAutomatic = @{
            Name                    = $script:testServiceName
            StartupType             = $script:testServiceStartupType
            BuiltInAccount          = 'LocalSystem'
            DesktopInteract         = $true
            State                   = $script:testServiceStatusRunning
            Ensure                  = 'Present'
            Path                    = $script:testServiceExecutablePath
            DisplayName             = $script:testServiceDisplayName
            Description             = $script:testServiceDescription
        }

        Describe "$script:DscResourceName\Get-TargetResource" {
            Context 'Service exists' {
                # Mocks that should be called
                Mock -CommandName Test-ServiceExists `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Get-ServiceResource `
                     -MockWith { $script:testServiceMockRunning } `
                     -Verifiable

                Mock -CommandName Get-Win32ServiceObject `
                     -MockWith { $script:testWin32ServiceMockRunningLocalSystem } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:service = Get-TargetResource `
                            -Name $script:testServiceName `
                    } | Should Not Throw
                }

                It 'Should return the correct hashtable properties' {
                    $service.Ensure          | Should Be 'Present'
                    $service.Name            | Should Be $script:testServiceName
                    $service.StartupType     | Should Be $script:testServiceStartupType
                    $service.BuiltInAccount  | Should Be 'LocalSystem'
                    $service.State           | Should Be $script:testServiceStatusRunning
                    $service.Path            | Should Be $script:testServiceExecutablePath
                    $service.DisplayName     | Should Be $script:testServiceDisplayName
                    $service.Description     | Should Be $script:testServiceDescription
                    $service.DesktopInteract | Should Be $true
                    $service.Dependencies    | Should Be $script:testServiceDependsOn
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                }
            }

            Context 'Service does not exist' {
                # Mocks that should be called
                Mock -CommandName Test-ServiceExists -MockWith { $false } -Verifiable

                # Mocks that should not be called
                Mock -CommandName Get-serviceResource

                Mock -CommandName Get-Win32ServiceObject

                It 'Should not throw an exception' {
                    { $script:service = Get-TargetResource `
                        -Name $script:testServiceName `
                    } | Should Not Throw
                }

                It 'Should return the correct hashtable properties' {
                    $service.Ensure          | Should Be 'Absent'
                    $service.Name            | Should Be $script:testServiceName
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-serviceResource -Exactly 0
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 0
                }
            }
        }

        Describe "$script:DscResourceName\Set-TargetResource" {
            Context 'Service exists and should not' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Stop-ServiceResource `
                     -Verifiable

                Mock -CommandName Remove-Service `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Start-ServiceResource
                Mock -CommandName New-Service
                Mock -CommandName Compare-ServicePath
                Mock -CommandName Write-WriteProperty

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.Ensure = 'Absent'
                    { Set-TargetResource @Splat } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Remove-Service -Exactly 1
                    Assert-MockCalled -CommandName New-Service -Exactly 0
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 0
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 0
                }
            }

            Context 'Service exists and should, should be running, all parameters passed' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Compare-ServicePath `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Write-WriteProperty `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Start-ServiceResource `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Stop-ServiceResource
                Mock -CommandName New-Service
                Mock -CommandName Remove-Service

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    { Set-TargetResource @Splat } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Remove-Service -Exactly 0
                    Assert-MockCalled -CommandName New-Service -Exactly 0
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 1
                }
            }

            Context 'Service exists and should, should be running, path needs change' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Compare-ServicePath `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Write-WriteProperty `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Start-ServiceResource `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Stop-ServiceResource
                Mock -CommandName New-Service
                Mock -CommandName Remove-Service

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.Path = 'c:\NewServicePath.exe'
                    { Set-TargetResource @Splat } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Remove-Service -Exactly 0
                    Assert-MockCalled -CommandName New-Service -Exactly 0
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 1
                }
            }

            Context 'Service exists and should, should be running but needs restart, all parameters passed' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $true } `
                     -Verifiable

                Mock `
                    -CommandName Compare-ServicePath `
                    -MockWith { $true } `
                    -Verifiable

                Mock -CommandName Write-WriteProperty `
                     -MockWith { $true } `
                     -Verifiable

                Mock `
                    -CommandName Start-ServiceResource `
                    -Verifiable

                Mock -CommandName Stop-ServiceResource `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName New-Service
                Mock -CommandName Remove-Service

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    { Set-TargetResource @Splat } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Remove-Service -Exactly 0
                    Assert-MockCalled -CommandName New-Service -Exactly 0
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 1
                }
            }

            Context 'Service exists and should, should be stopped, all parameters passed' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Compare-ServicePath `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Write-WriteProperty `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Stop-ServiceResource `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName New-Service
                Mock -CommandName Remove-Service
                Mock -CommandName Start-ServiceResource

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.State = 'Stopped'
                    { Set-TargetResource @Splat } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Remove-Service -Exactly 0
                    Assert-MockCalled -CommandName New-Service -Exactly 0
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 1
                }
            }

             Context 'Service exists and should, State is Ignore, all parameters passed' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Compare-ServicePath `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Write-WriteProperty `
                     -MockWith { $false } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName New-Service
                Mock -CommandName Remove-Service
                Mock -CommandName Start-ServiceResource
                Mock -CommandName Stop-ServiceResource

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.State = 'Ignore'
                    { Set-TargetResource @Splat } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Remove-Service -Exactly 0
                    Assert-MockCalled -CommandName New-Service -Exactly 0
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 1
                }
            }

            Context 'Service does not exist but should' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName New-Service `
                     -Verifiable

                Mock -CommandName Write-WriteProperty `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Start-ServiceResource `
                     -MockWith { $false } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Compare-ServicePath
                Mock -CommandName Remove-Service
                Mock -CommandName Stop-ServiceResource

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    { Set-TargetResource @Splat } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Remove-Service -Exactly 0
                    Assert-MockCalled -CommandName New-Service -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 0
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 1
                }
            }

            Context 'Service does not exist but should, but no path specified' {
                # Mocks that should be called
                Mock -CommandName Test-StartupType `
                     -Verifiable

                Mock -CommandName Test-ServiceExists `
                     -MockWith { $false } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName New-Service
                Mock -CommandName Compare-ServicePath
                Mock -CommandName Start-ServiceResource
                Mock -CommandName Remove-Service
                Mock -CommandName Stop-ServiceResource
                Mock -CommandName Write-WriteProperty

                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ServiceDoesNotExistPathMissingError' `
                    -ErrorMessage ($script:localizedData.ServiceDoesNotExistPathMissingError `
                                    -f $script:testServiceName)

                It 'Should throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.Remove('Path')
                    { Set-TargetResource @Splat } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Stop-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Remove-Service -Exactly 0
                    Assert-MockCalled -CommandName New-Service -Exactly 0
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 0
                    Assert-MockCalled -CommandName Write-WriteProperty -Exactly 0
                }
            }
        }

        Describe "$script:DscResourceName\Test-TargetResource" {
            # Mocks that should be called
            Mock -CommandName Test-ServiceExists `
                 -MockWith { $true } `
                 -Verifiable

            Mock -CommandName Test-StartupType `
                 -Verifiable

            Mock -CommandName Get-ServiceResource `
                 -MockWith { $script:testServiceMockRunning } `
                 -Verifiable

            Mock -CommandName Get-Win32ServiceObject `
                 -MockWith { $script:testWin32ServiceMockRunningLocalSystem } `
                 -Verifiable

            Mock -CommandName Compare-ServicePath `
                 -MockWith { $true } `
                 -Verifiable

            Mock -CommandName Test-UserName `
                 -MockWith { $true } `
                 -Verifiable

            Context 'Service exists and should, and all parameters match' {
                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    { $script:result = Test-TargetResource @Splat } | Should Not Throw
                }

                It 'Should return true' {
                    $script:result | Should Be $true
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                }
            }

            Context 'Service exists and should, path mistmatches' {
                # Mocks that should be called
                Mock -CommandName Compare-ServicePath `
                     -MockWith { $false } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Test-UserName

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.Path = 'c:\ANewPath.exe'
                    { $script:result = Test-TargetResource @Splat } | Should Not Throw
                }

                It 'Should return false' {
                    $script:result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 0
                }
            }

            Context 'Service exists and should, startup type mistmatches' {
                # Mocks that should be called
                Mock -CommandName Test-UserName `
                     -MockWith { $true } `
                     -Verifiable

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.StartupType = 'Manual'
                    { $script:result = Test-TargetResource @Splat } | Should Not Throw
                }

                It 'Should return false' {
                    $script:result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                }
            }

            Context 'Service exists and should, credential mistmatches' {
                # Mocks that should be called
                Mock -CommandName Test-UserName `
                     -MockWith { $false } `
                     -Verifiable

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.Credential = $script:testNewCredential
                    { $script:result = Test-TargetResource @Splat } | Should Not Throw
                }

                It 'Should return false' {
                    $script:result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                }
            }

            Context 'Service exists and should, is running but should be stopped' {
                # Mocks that should be called
                Mock -CommandName Compare-ServicePath `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $true } `
                     -Verifiable

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.State = 'Stopped'
                    { $script:result = Test-TargetResource @Splat } | Should Not Throw
                }

                It 'Should return false' {
                    $script:result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                }
            }

            Context 'Service exists and should, everything matches and State is set to Ignore' {
                # Mocks that should be called
                Mock -CommandName Compare-ServicePath `
                     -MockWith { $true } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $true } `
                     -Verifiable

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.State = 'Ignore'
                    { $script:result = Test-TargetResource @Splat } | Should Not Throw
                }

                It 'Should return true' {
                    $script:result | Should Be $true
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                }
            }

            Context 'Service exists and should not' {
                # Mocks that should not be called
                Mock -CommandName Compare-ServicePath
                Mock -CommandName Test-UserName
                Mock -CommandName Get-ServiceResource
                Mock -CommandName Get-Win32ServiceObject

                It 'Should not throw an exception' {
                    $Splat = $script:splatServiceExistsAutomatic.Clone()
                    $Splat.Ensure = 'Absent'
                    { $script:result = Test-TargetResource @Splat } | Should Not Throw
                }

                It 'Should return false' {
                    $script:result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 0
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 0
                    Assert-MockCalled -CommandName Test-StartupType -Exactly 1
                    Assert-MockCalled -CommandName Compare-ServicePath -Exactly 0
                    Assert-MockCalled -CommandName Test-UserName -Exactly 0
                }
            }
        }

        Describe "$script:DscResourceName\Test-StartupType" {
            Context 'Service is stopped, startup is automatic' {
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'CannotStopServiceSetToStartAutomatically' `
                    -ErrorMessage ($script:localizedData.CannotStopServiceSetToStartAutomatically `
                        -f $script:testServiceName)

                It 'Should throw CannotStopServiceSetToStartAutomatically exception' {
                    { Test-StartupType `
                        -Name $script:testServiceName `
                        -StartupType 'Automatic' `
                        -State 'Stopped' `
                    } | Should Throw $errorRecord
                }
            }

            Context 'Service is stopped, startup is not automatic' {
                It 'Should not throw an exception' {
                    { Test-StartupType `
                        -Name $script:testServiceName `
                        -StartupType 'Disabled' `
                        -State 'Stopped' `
                    } | Should Not Throw
                }
            }

            Context 'Service is running, startup is disabled' {
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'CannotStartAndDisable' `
                    -ErrorMessage ($script:localizedData.CannotStartAndDisable -f $script:testServiceName)

                It 'Should throw CannotStartAndDisable exception' {
                    { Test-StartupType `
                        -Name $script:testServiceName `
                        -StartupType 'Disabled' `
                        -State 'Running' `
                    } | Should Throw $errorRecord
                }
            }

            Context 'Service is running, startup is not disabled' {
                It 'Should not throw exception' {
                    { Test-StartupType `
                        -Name $script:testServiceName `
                        -StartupType 'Manual' `
                        -State 'Running' `
                    } | Should Not Throw
                }
            }

            Context 'State is Ignore' {
                It 'Should not throw exception for Disabled' {
                    { Test-StartupType `
                        -Name $script:testServiceName `
                        -StartupType 'Disabled' `
                        -State 'Ignore' `
                    } | Should Not Throw
                }

                It 'Should not throw exception for Automatic' {
                    { Test-StartupType `
                        -Name $script:testServiceName `
                        -StartupType 'Automatic' `
                        -State 'Ignore' `
                    } | Should Not Throw
                }
            }
        }

        Describe "$script:DscResourceName\ConvertTo-StartModeString" {
            Context 'StartupType is Automatic' {
                It 'Should return Auto' {
                    ConvertTo-StartModeString -StartupType 'Automatic' | Should Be 'Auto'
                }
            }

            Context 'StartupType is Disabled' {
                It 'Should return Disabled' {
                    ConvertTo-StartModeString -StartupType 'Disabled' | Should Be 'Disabled'
                }
            }
        }

        Describe "$script:DscResourceName\ConvertTo-StartupTypeString" {
            Context 'StartupType is Auto' {
                It 'Should return Automatic' {
                    ConvertTo-StartupTypeString -StartMode 'Auto' | Should Be 'Automatic'
                }
            }

            Context 'StartupType is Disabled' {
                It 'Should return Disabled' {
                    ConvertTo-StartupTypeString -StartMode 'Disabled' | Should Be 'Disabled'
                }
            }
        }

        Describe "$script:DscResourceName\Get-Win32ServiceObject" {
            Context 'Service exists' {
                Mock -CommandName Get-CimInstance `
                     -MockWith { $script:testWin32ServiceMockRunningLocalSystem } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:result = Get-Win32ServiceObject `
                                            -Name $script:testServiceName } | Should Not Throw
                }

                It 'Should return expected hash table' {
                    $script:result = $script:testWin32ServiceMockRunningLocalSystem
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-CimInstance -Exactly 1
                }
            }

            Context 'Service does not exist' {
                Mock -CommandName Get-CimInstance `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:result = Get-Win32ServiceObject `
                        -Name $script:testServiceName } | Should Not Throw
                }

                It 'Should return $null' {
                    $script:result | Should BeNullOrEmpty
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-CimInstance -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Set-ServiceStartMode" {
            # Stub Functions for Mocking
            function Invoke-CimMethod { param ( $InputObject, $MethodName, $Arguments ) }

            Context 'Current StartMode is set to Auto and should be' {
                Mock -CommandName Invoke-CimMethod

                It 'Should not throw an exception' {
                    { Set-ServiceStartMode `
                        -Win32ServiceObject $script:testWin32ServiceMockRunningLocalSystem `
                        -StartupType $script:testServiceStartupType `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 0
                }
            }

            Context 'Current StartMode needs to be changed, and is changed successfully' {
                Mock -CommandName Invoke-CimMethod `
                     -MockWith { return @{ ReturnValue = 0 } } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { Set-ServiceStartMode `
                        -Win32ServiceObject $script:testWin32ServiceMockRunningLocalSystem `
                        -StartupType 'Manual' `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'Current StartMode needs to be changed but an error occured' {
                Mock -CommandName Invoke-CimMethod `
                     -MockWith { return @{ ReturnValue = 99 } } `
                     -Verifiable

                $innerMessage = ($script:localizedData.MethodFailed `
                    -f 'Change', 'Win32_Service', '99' )
                $errorMessage = ($script:localizedData.ErrorChangingProperty `
                    -f 'StartupType', $innerMessage)
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ChangeStartupTypeFailed' `
                    -ErrorMessage $errorMessage

                It 'Should throw an exception' {
                    { Set-ServiceStartMode `
                        -Win32ServiceObject $script:testWin32ServiceMockRunningLocalSystem `
                        -StartupType 'Manual' `
                    } | Should Throw $errorMessage
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Write-WriteProperty" {
            # Stub Functions for Mocking
            function Invoke-CimMethod { param ( $InputObject, $MethodName, $Arguments ) }

            # Mocks that should be called
            Mock -CommandName Get-Win32ServiceObject `
                 -MockWith { $script:testServiceStartupTypeWin32 } `
                 -Verifiable

            Mock -CommandName Get-Service `
                 -MockWith { $script:testServiceMockRunning } `
                 -Verifiable

            # Mocks that should not be called
            Mock -CommandName Set-Service

            Context 'No parameters passed' {
                It 'Should not throw an exception' {
                    { Write-WriteProperty -Name $script:testServiceName } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Set-Service -Exactly 0
                }
            }

            Context 'Different DisplayName passed, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Set-Service `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -DisplayName 'NewDisplayName' `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Set-Service -Exactly 1
                }
            }

            Context 'Different Description passed, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Set-Service `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -Description 'NewDescription' `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Set-Service -Exactly 1
                }
            }

            Context 'Different Dependencies passed and set successfully, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ ReturnValue = 0 } } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -Dependencies 'DepService1','DepService2' `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'Different Dependencies passed and set failed, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ ReturnValue = 99 } } `
                     -Verifiable

                $innerMessage = ($script:localizedData.MethodFailed `
                    -f 'Change','Win32_Service','99')
                $errorMessage = ($script:localizedData.ErrorChangingProperty `
                    -f 'Dependencies',$innerMessage)
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ChangeCredentialFailed' `
                    -ErrorMessage $errorMessage

                It 'Should throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -Dependencies 'DepService1','DepService2' `
                    } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'Path passed, will trigger restart' {
                # Mocks that should be called
                Mock -CommandName Write-BinaryProperty `
                     -MockWith { $true } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -Path 'c:\NewExecutable.exe' `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Write-BinaryProperty -Exactly 1
                }
            }

            Context 'StartupType passed, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Set-ServiceStartMode `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -StartupType 'Manual' `
                    } | Should Not Throw
                }

                It 'Should return false' {
                    $script:Result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Set-ServiceStartMode -Exactly 1
                }
            }

            Context 'Credential passed, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Write-CredentialProperty `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -Credential $script:testCredential `
                    } | Should Not Throw
                }

                It 'Should return false' {
                    $script:Result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Write-CredentialProperty -Exactly 1
                }
            }

            Context 'BuildinAccount passed, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Write-CredentialProperty `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -BuiltInAccount 'LocalSystem' `
                    } | Should Not Throw
                }

                It 'Should return false' {
                    $script:Result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Write-CredentialProperty -Exactly 1
                }
            }

            Context 'DesktopInteract passed, will not trigger restart' {
                # Mocks that should be called
                Mock -CommandName Write-CredentialProperty `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:Result = Write-WriteProperty `
                        -Name $script:testServiceName `
                        -DesktopInteract $true `
                    } | Should Not Throw
                }

                It 'Should return false' {
                    $script:Result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-Win32ServiceObject -Exactly 1
                    Assert-MockCalled -CommandName Get-Service -Exactly 1
                    Assert-MockCalled -CommandName Write-CredentialProperty -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Write-CredentialProperty" {
            # Dummy Functions
            function Invoke-CimMethod { param ( $InputObject, $MethodName, $Arguments ) }

            Context 'No parameters to be changed passed in' {
                # Mocks that should not be called
                Mock -CommandName Get-UserNameAndPassword
                Mock -CommandName Test-UserName
                Mock -CommandName Set-LogOnAsServicePolicy
                Mock -CommandName Invoke-CimMethod

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 0
                    Assert-MockCalled -CommandName Test-UserName -Exactly 0
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 0
                }
            }

            Context 'Desktop interact passed but does not need to be changed' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { $null, $null } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Test-UserName
                Mock -CommandName Set-LogOnAsServicePolicy
                Mock -CommandName Invoke-CimMethod

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -DesktopInteract $true `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 0
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 0
                }
            }

            Context 'Desktop interact passed and does need to be changed' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { $null, $null } `
                     -Verifiable

                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 0 } } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Test-UserName
                Mock -CommandName Set-LogOnAsServicePolicy

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -DesktopInteract $false `
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 0
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'Desktop interact passed and does need to be changed but fails' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { $null,$null } `
                     -Verifiable

                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 99 } } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Test-UserName
                Mock -CommandName Set-LogOnAsServicePolicy

                $innerMessage = ($script:localizedData.MethodFailed `
                    -f 'Change','Win32_Service','99')
                $errorMessage = ($script:localizedData.ErrorChangingProperty `
                    -f 'Credential',$innerMessage)
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ChangeCredentialFailed' `
                    -ErrorMessage $errorMessage

                It 'Should throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -DesktopInteract $false `
                    } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 0
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'Both credential and BuiltInAccount passed' {
                # Mocks that should not be called
                Mock -CommandName Get-UserNameAndPassword
                Mock -CommandName Invoke-CimMethod
                Mock -CommandName Test-UserName
                Mock -CommandName Set-LogOnAsServicePolicy

                $errorRecord = Get-InvalidArgumentRecord `
                   -ErrorId 'OnlyCredentialOrBuiltInAccount' `
                    -ErrorMessage ($script:localizedData.OnlyOneParameterCanBeSpecified `
                    -f 'Credential','BuiltInAccount')

                It 'Should throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Credential $script:testCredential `
                        -BuiltInAccount 'LocalSystem' } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 0
                    Assert-MockCalled -CommandName Test-UserName -Exactly 0
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 0
                }
            }

            Context 'Credential passed but does not need to be changed' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { $script:testUsername,$script:testPassword } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $true } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Set-LogOnAsServicePolicy
                Mock -CommandName Invoke-CimMethod

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Credential $script:testCredential } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 0
                }
            }

            Context 'Credential passed and needs to be changed' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { $script:testUsername,$script:testPassword } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Set-LogOnAsServicePolicy `
                     -Verifiable

                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 0 } } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Credential $script:testCredential } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 1
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'Credential passed and needs to be changed, but throws exception' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { $script:testUsername,$script:testPassword } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Set-LogOnAsServicePolicy `
                     -Verifiable

                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 99 } } `
                     -Verifiable

                $innerMessage = ($script:localizedData.MethodFailed `
                    -f 'Change','Win32_Service','99')
                $errorMessage = ($script:localizedData.ErrorChangingProperty `
                    -f 'Credential',$innerMessage)
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ChangeCredentialFailed' `
                    -ErrorMessage $errorMessage

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Credential $script:testCredential } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 1
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'BuiltInAccount passed but does not need to be changed' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { '.\LocalSystem','' } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $true } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Set-LogOnAsServicePolicy
                Mock -CommandName Invoke-CimMethod

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -BuiltInAccount 'LocalSystem' } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 0
                }
            }

            Context 'BuiltInAccount passed and needs to be changed' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { '.\LocalSystem',$null } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 0 } } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Set-LogOnAsServicePolicy

                It 'Should not throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -BuiltInAccount 'LocalSystem' } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'BuiltInAccount passed and needs to be changed, but throws exception' {
                # Mocks that should be called
                Mock -CommandName Get-UserNameAndPassword `
                     -MockWith { '.\LocalSystem',$null } `
                     -Verifiable

                Mock -CommandName Test-UserName `
                     -MockWith { $false } `
                     -Verifiable

                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 99 } } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName Set-LogOnAsServicePolicy

                $innerMessage = ($script:localizedData.MethodFailed `
                    -f 'Change','Win32_Service','99')
                $errorMessage = ($script:localizedData.ErrorChangingProperty `
                    -f 'Credential',$innerMessage)
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ChangeCredentialFailed' `
                    -ErrorMessage $errorMessage

                It 'Should throw an exception' {
                    { Write-CredentialProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -BuiltInAccount 'LocalSystem' } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-UserNameAndPassword -Exactly 1
                    Assert-MockCalled -CommandName Test-UserName -Exactly 1
                    Assert-MockCalled -CommandName Set-LogOnAsServicePolicy -Exactly 0
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Write-BinaryProperty" {
            # Stub Functions for Mocking
            function Invoke-CimMethod { param ( $InputObject, $MethodName, $Arguments ) }

            Context 'Path is already correct' {
                # Mocks that should not be called
                Mock -CommandName Invoke-CimMethod

                It 'Should not throw an exception' {
                    { $script:result = Write-BinaryProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Path $script:testServiceExecutablePath } | Should Not Throw
                }

                It 'Should return false' {
                    $script:result = $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 0
                }
            }

            Context 'Path needs to be changed and is changed without error' {
                # Mocks that should be called
                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 0 } } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:result = Write-BinaryProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Path 'c:\NewServicePath.exe' } | Should Not Throw
                }

                It 'Should return true' {
                    $script:result = $true
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }

            Context 'Path needs to be changed but an error occurs changing it' {
                # Mocks that should be called
                Mock -CommandName Invoke-CimMethod `
                     -MockWith { @{ returnValue = 99 } } `
                     -Verifiable

                $innerMessage = ($script:localizedData.MethodFailed `
                    -f 'Change', 'Win32_Service', 99)
                $errorMessage = ($script:localizedData.ErrorChangingProperty `
                    -f 'Binary Path', $innerMessage)
                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ChangeBinaryPathFailed' `
                    -ErrorMessage $errorMessage

                It 'Should throw an exception' {
                    { $script:result = Write-BinaryProperty `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Path 'c:\NewServicePath.exe' } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Invoke-CimMethod -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Test-UserName" {
            Context 'Username matches' {
                It 'Should not throw an exception' {
                    { $script:result = Test-Username `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Username $script:testUsername } | Should Not Throw
                }

                It 'Should return true' {
                    $script:result = $true
                }
            }

            Context 'Username does not match' {
                It 'Should not throw an exception' {
                    { $script:result = Test-Username `
                        -ServiceWmi $script:testWin32ServiceMockRunningLocalSystem `
                        -Username 'mismatch' } | Should Not Throw
                }

                It 'Should return false' {
                    $script:result = $false
                }
            }
        }

        Describe "$script:DscResourceName\Get-UserNameAndPassword" {
            Context 'Built-in account provided' {
                $script:result = Get-UserNameAndPassword -BuiltInAccount 'LocalService'

                It 'Should return: NT Authority\LocalService and $null' {
                     $script:result[0] | Should Be 'NT Authority\LocalService'
                     $script:result[1] | Should BeNullOrEmpty
                }
            }

            Context 'Credential provided' {
                $script:result = Get-UserNameAndPassword -Credential $script:testCredential

                It 'Should return the correct username and password' {
                     $script:result[0] | Should Be ".\$script:testUsername"
                     $script:result[1] | Should Be $script:testPassword
                }
            }

            Context 'Neither built-in account or credential provided' {
                $script:result = Get-UserNameAndPassword

                It 'Should return both results as null/empty' {
                     $script:result[0] | Should BeNullOrEmpty
                     $script:result[1] | Should BeNullOrEmpty
                }
            }
        }

        Describe "$script:DscResourceName\Remove-Service" {
            # Mocks that should be called
            Mock -CommandName 'sc.exe' -Verifiable
            Mock -CommandName Test-ServiceExists -MockWith { $false } -Verifiable

            Context 'Service is deleted successfully' {
                # Mocks that should not be called
                Mock -CommandName Start-Sleep

                It 'Should not throw exception' {
                    {
                        Remove-Service -Name $script:testServiceName
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName 'sc.exe' -Exactly 1
                    Assert-MockCalled -CommandName Test-ServiceExists -Exactly 1
                    Assert-MockCalled -CommandName Start-Sleep -Exactly 0
                }
            }

            

            Context 'Service can not be deleted (will take 5 seconds)' {
                Mock -CommandName Test-ServiceExists -MockWith { $true } -Verifiable

                Mock -CommandName Start-Sleep -Verifiable

                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ErrorDeletingService' `
                    -ErrorMessage ($script:localizedData.ErrorDeletingService -f $script:testServiceName)

                It 'Should throw ErrorDeletingService exception' {
                    { Remove-Service -Name $script:testServiceName } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName 'sc.exe' -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Start-ServiceResource" {
            Context 'Service is already running' {
                # Mocks that should be called
                Mock -CommandName Get-ServiceResource `
                     -MockWith { $script:testServiceMockRunning } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName New-Object

                It 'Should not throw exception' {
                    {
                        Start-ServiceResource -Name $script:testServiceName -StartUpTimeout 30000
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName New-Object -Exactly 0
                }
            }

            Context 'Service is stopped' {
                # Mocks that should be called
                Mock -CommandName Get-ServiceResource `
                     -MockWith { $script:testServiceMockStopped } `
                     -Verifiable

                Mock -CommandName New-Object `
                     -Verifiable

                $global:ServiceStarted = $false

                It 'Should not throw exception' {
                    {
                        Start-ServiceResource -Name $script:testServiceName -StartUpTimeout 30000
                    } | Should Not Throw
                }

                It 'Should call start method' {
                    $global:ServiceStarted | Should Be $true
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName New-Object -Exactly 1
                }

                Remove-Variable -Name ServiceStarted -Scope Global
            }
        }

        Describe "$script:DscResourceName\Stop-ServiceResource" {
            Context 'Service is already stopped' {
                # Mocks that should be called
                Mock -CommandName Get-ServiceResource `
                     -MockWith { $script:testServiceMockStopped } `
                     -Verifiable

                # Mocks that should not be called
                Mock -CommandName New-Object

                It 'Should not throw exception' {
                    {
                        Stop-ServiceResource -Name $script:testServiceName -TerminateTimeout 30000
                    } | Should Not Throw
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName New-Object -Exactly 0
                }
            }

            Context 'Service is running' {
                # Mocks that should be called
                Mock -CommandName Get-ServiceResource `
                     -MockWith { $script:testServiceMockRunning } `
                     -Verifiable

                Mock -CommandName New-Object `
                     -Verifiable

                $global:ServiceStopped = $false

                It 'Should not throw exception' {
                    {
                        Stop-ServiceResource -Name $script:testServiceName -TerminateTimeout 30000
                    } | Should Not Throw
                }

                It 'Should call stop method' {
                    $global:ServiceStopped | Should Be $true
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled -CommandName Get-ServiceResource -Exactly 1
                    Assert-MockCalled -CommandName New-Object -Exactly 1
                }

                Remove-Variable -Name ServiceStopped -Scope Global
            }
        }

        Describe "$script:DscResourceName\Resolve-UserName" {
            Context 'Username is NetworkService' {
                It 'Should return NT Authority\NetworkService' {
                    Resolve-UserName -Username 'NetworkService' | Should Be 'NT Authority\NetworkService'
                }
            }

            Context 'Username is LocalService' {
                It 'Should return NT Authority\LocalService' {
                    Resolve-UserName -Username 'LocalService' | Should Be 'NT Authority\LocalService'
                }
            }

            Context 'Username is LocalSystem' {
                It 'Should return .\LocalSystem' {
                    Resolve-UserName -Username 'LocalSystem' | Should Be '.\LocalSystem'
                }
            }

            Context 'Username is Domain\svcAccount' {
                It 'Should return Domain\svcAccount' {
                    Resolve-UserName -Username 'Domain\svcAccount' | Should Be 'Domain\svcAccount'
                }
            }

            Context 'Username is svcAccount' {
                It 'Should return .\svcAccount' {
                    Resolve-UserName -Username 'svcAccount' | Should Be '.\svcAccount'
                }
            }
        }

        Describe "$script:DscResourceName\Test-ServiceExists" {
            Context 'Service exists' {
                # Mocks that should be called
                Mock -CommandName Get-Service `
                     -ParameterFilter { $Name -eq $script:testServiceName } `
                     -MockWith { $script:testServiceMockRunning } `
                     -Verifiable

                It 'Should not throw an exception' {
                    {
                        $script:result = Test-ServiceExists -Name $script:testServiceName
                    } | Should Not Throw
                }

                It 'Should return true' {
                    $script:Result | Should Be $true
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled `
                        -CommandName Get-Service `
                        -ParameterFilter { $Name -eq $script:testServiceName } `
                        -Exactly 1
                }
            }

            Context 'Service does not exist' {
                # Mocks that should be called
                Mock -CommandName Get-Service `
                     -ParameterFilter { $Name -eq $script:testServiceName } `
                     -Verifiable

                It 'Should not throw an exception' {
                    {
                        $script:result = Test-ServiceExists -Name $script:testServiceName
                    } | Should Not Throw
                }

                It 'Should return false' {
                    $script:Result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled `
                        -CommandName Get-Service `
                        -ParameterFilter { $Name -eq $script:testServiceName } `
                        -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Compare-ServicePath" {
            Context 'Service exists, path matches' {
                # Mocks that should be called
                Mock -CommandName Get-CimInstance `
                     -MockWith { $script:testWin32ServiceMockRunningLocalSystem } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:result = Compare-ServicePath `
                        -Name $script:testServiceName `
                        -Path $script:testServiceExecutablePath `
                    } | Should Not Throw
                }

                It 'Should return true' {
                    $script:Result | Should Be $true
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled `
                        -CommandName Get-CimInstance `
                        -Exactly 1
                }
            }

            Context 'Service exists, path does not match' {
                # Mocks that should be called
                Mock -CommandName Get-CimInstance `
                     -MockWith { $script:testWin32ServiceMockRunningLocalSystem } `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:result = Compare-ServicePath `
                        -Name $script:testServiceName `
                        -Path 'c:\differentpath' `
                    } | Should Not Throw
                }

                It 'Should return false' {
                    $script:Result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled `
                        -CommandName Get-CimInstance `
                        -Exactly 1
                }
            }

            Context 'Service does not exist' {
                # Mocks that should be called
                Mock -CommandName Get-CimInstance `
                     -Verifiable

                It 'Should not throw an exception' {
                    { $script:result = Compare-ServicePath `
                        -Name $script:testServiceName `
                        -Path 'c:\differentpath' `
                    } | Should Not Throw
                }

                It 'Should return false' {
                    $script:Result | Should Be $false
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled `
                        -CommandName Get-CimInstance `
                        -Exactly 1
                }
            }
        }

        Describe "$script:DscResourceName\Get-ServiceResource" {
            Context 'Service exists' {
                # Mocks that should be called
                Mock -CommandName Get-Service `
                     -ParameterFilter { $Name -eq $script:testServiceName } `
                     -MockWith { $script:testServiceMockRunning } `
                     -Verifiable

                It 'Should not throw an exception' {
                    {
                        $script:service = Get-ServiceResource -Name $script:testServiceName
                    } | Should Not Throw
                }

                It 'Should return the correct hashtable properties' {
                    $script:service.Name               | Should Be $script:testServiceName
                    $script:service.ServiceName        | Should Be $script:testServiceName
                    $script:service.DisplayName        | Should Be $script:testServiceDisplayName
                    $script:service.StartType          | Should Be $script:testServiceStartupType
                    $script:service.Status             | Should Be $script:testServiceStatusRunning
                    $script:service.ServicesDependedOn | Should Be $script:testServiceDependsOnHash
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled `
                        -CommandName Get-Service `
                        -ParameterFilter { $Name -eq $script:testServiceName } `
                        -Exactly 1
                }
            }

            Context 'Service does not exist' {
                # Mocks that should be called
                Mock -CommandName Get-Service `
                     -ParameterFilter { $Name -eq $script:testServiceName } `
                     -Verifiable

                $errorRecord = Get-InvalidArgumentRecord `
                    -ErrorId 'ServiceNotFound' `
                    -ErrorMessage ($script:localizedData.ServiceNotFound -f $script:testServiceName)

                It 'Should throw a ServiceNotFound exception' {
                    {
                        $script:service = Get-ServiceResource -Name $script:testServiceName
                    } | Should Throw $errorRecord
                }

                It 'Should call expected Mocks' {
                    Assert-VerifiableMocks
                    Assert-MockCalled `
                        -CommandName Get-Service `
                        -ParameterFilter { $Name -eq $script:testServiceName } `
                        -Exactly 1
                }
            }
        }
    }
}
finally
{
    Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment
}