Tests/Integration/MSFT_xServiceResource.Integration.Tests.ps1

<#
    Please note that these tests are currently dependent on each other.
    They must be run in the order given and if one test fails, subsequent tests will
    also fail.
#>


$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 'Integration')
{
    return
}

$script:testEnvironment = Enter-DscResourceTestEnvironment `
    -DscResourceModuleName 'xPSDesiredStateConfiguration' `
    -DscResourceName 'MSFT_xServiceResource' `
    -TestType 'Integration'

try
{
    Describe 'xService Integration Tests' {
        BeforeAll {
            # Import CommonResourceHelper for Test-IsNanoServer, Get-TestAdministratorAccountCredential
            $moduleRootFilePath = Split-Path -Path $script:testsFolderFilePath -Parent
            $dscResourcesFolderFilePath = Join-Path -Path $moduleRootFilePath -ChildPath 'DscResources'
            $commonResourceHelperFilePath = Join-Path -Path $dscResourcesFolderFilePath -ChildPath 'CommonResourceHelper.psm1'
            Import-Module -Name $commonResourceHelperFilePath

            # Import DscResource.Tests TestHelper for Reset-Dsc
            $dscResourceTestsFilePath = Join-Path -Path $moduleRootFilePath -ChildPath 'DscResource.Tests'
            $dscResourceTestHelperFilePath = Join-Path -Path $dscResourceTestsFilePath -ChildPath 'TestHelper.psm1'
            Import-Module -Name $dscResourceTestHelperFilePath

            # Import xService test helper for New-ServiceBinary, Test-ServiceExists, Remove-ServiceWithTimeout
            $serviceTestHelperFilePath = Join-Path -Path $script:testsFolderFilePath -ChildPath 'MSFT_xServiceResource.TestHelper.psm1'
            Import-Module -Name $serviceTestHelperFilePath

            # Import xService resource module for Test-TargetResource
            $serviceResourceFolderFilePath = Join-Path -Path $dscResourcesFolderFilePath -ChildPath 'MSFT_xServiceResource'
            $serviceResourceModuleFilePath = Join-Path -Path $serviceResourceFolderFilePath -ChildPath 'MSFT_xServiceResource.psm1'
            Import-Module -Name $serviceResourceModuleFilePath

            # Configuration file paths
            $script:configurationAllExceptCredentialFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_xServiceResource_AllExceptCredential.config.ps1'
            $script:configurationCredentialOnlyFilePath = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_xServiceResource_CredentialOnly.config.ps1'

            # Create test service binary to be the existing service
            $script:existingServiceProperties = @{
                Name = 'TestService'
                DisplayName = 'TestDisplayName'
                Description = 'Test service description'
                Dependencies = @( 'winrm' )
                Path = Join-Path -Path $TestDrive -ChildPath 'DscTestService.exe'
            }

            $existingServiceNewExecutableParameters = @{
                ServiceName = $script:existingServiceProperties.Name
                ServiceCodePath = Join-Path -Path $script:testsFolderFilePath -ChildPath 'DscTestService.cs'
                ServiceDisplayName = $script:existingServiceProperties.DisplayName
                ServiceDescription = $script:existingServiceProperties.Description
                ServiceDependsOn = $script:existingServiceProperties.Dependencies -join ', '
                OutputPath = $script:existingServiceProperties.Path
            }

            New-ServiceExecutable @existingServiceNewExecutableParameters

            # Create test service binary to be the new service with the same name as the existing service
            $script:newServiceProperties = @{
                Name = $script:existingServiceProperties.Name
                DisplayName = 'NewTestDisplayName'
                Description = 'New test service description'
                Dependencies = @( 'spooler' )
                Path = Join-Path -Path $TestDrive -ChildPath 'NewDscTestService.exe'
            }

            if (Test-IsNanoServer)
            {
                # Nano Server does not recognize 'spooler', so keep the dependencies value as 'winrm'
                $newServiceProperties['Dependencies'] = @( 'winrm' )
            }

            $newServiceNewExecutableParameters = @{
                ServiceName = $script:newServiceProperties.Name
                ServiceCodePath = Join-Path -Path $script:testsFolderFilePath -ChildPath 'DscTestServiceNew.cs'
                ServiceDisplayName = $script:newServiceProperties.DisplayName
                ServiceDescription = $script:newServiceProperties.Description
                ServiceDependsOn = $script:newServiceProperties.Dependencies -join ', '
                OutputPath = $script:newServiceProperties.Path
            }

            New-ServiceExecutable @newServiceNewExecutableParameters

            $script:testServiceNames = @( $script:existingServiceProperties.Name )
            $script:testServiceExecutables = @( $script:existingServiceProperties.Path, $script:newServiceProperties.Path )
        }

        AfterAll {
            # Remove any created services
            foreach ($testServiceName in $script:testServiceNames)
            {
                if (Test-ServiceExists -Name $testServiceName)
                {
                    Remove-ServiceWithTimeout -Name $testServiceName
                }
            }
        }

        Context 'Create a service' {
            Reset-DSC

            $configurationName = 'TestCreateService'
            $resourceParameters = $script:existingServiceProperties

            It 'Should compile and apply the MOF without throwing' {
                {
                    . $script:configurationAllExceptCredentialFilePath -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @resourceParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }

            $service = Get-Service -Name $resourceParameters.Name -ErrorAction 'SilentlyContinue'
            $serviceCimInstance = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name='$($resourceParameters.Name)'" -ErrorAction 'SilentlyContinue'

            It 'Should have created a new service with the specified name' {
                 $service | Should -Not -Be $null
                 $serviceCimInstance | Should -Not -Be $null

                 $service.Name | Should -Be $resourceParameters.Name
                 $serviceCimInstance.Name | Should -Be $resourceParameters.Name
            }

            It 'Should have created a new service with the specified path' {
                $serviceCimInstance.PathName | Should -Be $resourceParameters.Path
            }

            It 'Should have created a new service with the specified display name' {
                $service.DisplayName | Should -Be $resourceParameters.DisplayName
            }

            It 'Should have created a new service with the specified description' {
                $serviceCimInstance.Description | Should -Be $resourceParameters.Description
            }

            It 'Should have created a new service with the specified dependencies' {
                $differentDependencies = Compare-Object -ReferenceObject $resourceParameters.Dependencies -DifferenceObject $service.ServicesDependedOn.Name
                $differentDependencies | Should -Be $null
            }

            It 'Should have created a new service with the default state as Running' {
                $service.Status | Should -Be 'Running'
            }

            It 'Should have created a new service with the default startup type as Auto' {
                $serviceCimInstance.StartMode | Should -Be 'Auto'
            }

            It 'Should have created a new service with the default startup account name as LocalSystem' {
                $serviceCimInstance.StartName | Should -Be 'LocalSystem'
            }

            It 'Should have created a new service with the default desktop interaction setting as False' {
                $serviceCimInstance.DesktopInteract | Should -Be $false
            }

            It 'Should return true from Test-TargetResource with the same parameters' {
                MSFT_xServiceResource\Test-TargetResource @resourceParameters | Should -Be $true
            }
        }

        Context 'Edit the service path, display name, description, and dependencies' {
            Reset-DSC

            $configurationName = 'TestCreateService'
            $resourceParameters = $script:newServiceProperties

            It 'Should compile and apply the MOF without throwing' {
                {
                    . $script:configurationAllExceptCredentialFilePath -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @resourceParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }

            $service = Get-Service -Name $resourceParameters.Name -ErrorAction 'SilentlyContinue'
            $serviceCimInstance = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name='$($resourceParameters.Name)'" -ErrorAction 'SilentlyContinue'

            It 'Should not have removed service with specified name' {
                 $service | Should -Not -Be $null
                 $serviceCimInstance | Should -Not -Be $null

                 $service.Name | Should -Be $resourceParameters.Name
                 $serviceCimInstance.Name | Should -Be $resourceParameters.Name
            }

            It 'Should have edited service to have the specified path' {
                $serviceCimInstance.PathName | Should -Be $resourceParameters.Path
            }

            It 'Should have edited service to have the specified display name' {
                $service.DisplayName | Should -Be $resourceParameters.DisplayName
            }

            It 'Should have edited service to have the specified description' {
                $serviceCimInstance.Description | Should -Be $resourceParameters.Description
            }

            It 'Should have edited service to have the specified dependencies' {
                $differentDependencies = Compare-Object -ReferenceObject $resourceParameters.Dependencies -DifferenceObject $service.ServicesDependedOn.Name
                $differentDependencies | Should -Be $null
            }

            It 'Should not have changed the service state from Running' {
                $service.Status | Should -Be 'Running'
            }

            It 'Should not have changed the service startup type from Auto' {
                $serviceCimInstance.StartMode | Should -Be 'Auto'
            }

            It 'Should not have changed the service startup account name from LocalSystem' {
                $serviceCimInstance.StartName | Should -Be 'LocalSystem'
            }

            It 'Should not have changed the service desktop interaction setting from False' {
                $serviceCimInstance.DesktopInteract | Should -Be $false
            }

            It 'Should return true from Test-TargetResource with the same parameters' {
                MSFT_xServiceResource\Test-TargetResource @resourceParameters | Should -Be $true
            }
        }

        Context 'Edit the service startup type and state' {
            Reset-DSC

            $configurationName = 'TestCreateService'
            $resourceParameters = @{
                Name = $script:existingServiceProperties.Name
                Path = $script:newServiceProperties.Path
                StartupType = 'Manual'
                State = 'Stopped'
            }

            It 'Should compile and apply the MOF without throwing' {
                {
                    . $script:configurationAllExceptCredentialFilePath -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @resourceParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }

            $service = Get-Service -Name $resourceParameters.Name -ErrorAction 'SilentlyContinue'
            $serviceCimInstance = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name='$($resourceParameters.Name)'" -ErrorAction 'SilentlyContinue'

            It 'Should not have removed service with specified name' {
                 $service | Should -Not -Be $null
                 $serviceCimInstance | Should -Not -Be $null

                 $service.Name | Should -Be $resourceParameters.Name
                 $serviceCimInstance.Name | Should -Be $resourceParameters.Name
            }

            It 'Should have edited the service to have the specified state' {
                $service.Status | Should -Be $resourceParameters.State
            }

            It 'Should have edited the service to have the specified startup type' {
                $serviceCimInstance.StartMode | Should -Be $resourceParameters.StartupType
            }

            It 'Should return true from Test-TargetResource with the same parameters' {
                MSFT_xServiceResource\Test-TargetResource @resourceParameters | Should -Be $true
            }
        }

        Context 'Edit the service start name and start password with Credential' {
            Reset-DSC

            $configData = @{
                AllNodes = @(
                    @{
                        NodeName = 'localhost'
                        PSDscAllowPlainTextPassword = $true
                    }
                )
            }

            $configurationName = 'TestCreateService'
            $resourceParameters = @{
                Name = 'TestService'
                Credential = Get-TestAdministratorAccountCredential
            }

            It 'Should compile and apply the MOF without throwing' {
                {
                    . $script:configurationCredentialOnlyFilePath -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive -ConfigurationData $configData @resourceParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }

            $service = Get-Service -Name $resourceParameters.Name -ErrorAction 'SilentlyContinue'
            $serviceCimInstance = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name='$($resourceParameters.Name)'" -ErrorAction 'SilentlyContinue'

            It 'Should not have removed service with specified name' {
                    $service | Should -Not -Be $null
                    $serviceCimInstance | Should -Not -Be $null

                    $service.Name | Should -Be $resourceParameters.Name
                    $serviceCimInstance.Name | Should -Be $resourceParameters.Name
            }

            It 'Should have edited the service to have the specified startup account name' {
                $expectedStartName = $resourceParameters.Credential.UserName

                if ($expectedStartName.StartsWith("$env:COMPUTERNAME\"))
                {
                    $expectedStartName = $expectedStartName.TrimStart("$env:COMPUTERNAME\")
                }

                $serviceCimInstance.StartName | Should -Be ".\$expectedStartName"
            }

            It 'Should return true from Test-TargetResource with the same parameters' {
                MSFT_xServiceResource\Test-TargetResource @resourceParameters | Should -Be $true
            }
        }

        Context 'Edit the service start name and start password with BuiltInAccount' {
            Reset-DSC

            $configurationName = 'TestCreateService'
            $resourceParameters = @{
                Name = 'TestService'
                Path = $script:newServiceProperties.Path
                BuiltInAccount = 'LocalService'
            }

            It 'Should compile and apply the MOF without throwing' {
                {
                    . $script:configurationAllExceptCredentialFilePath -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @resourceParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }

            $service = Get-Service -Name $resourceParameters.Name -ErrorAction 'SilentlyContinue'
            $serviceCimInstance = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name='$($resourceParameters.Name)'" -ErrorAction 'SilentlyContinue'

            It 'Should not have removed service with specified name' {
                 $service | Should -Not -Be $null
                 $serviceCimInstance | Should -Not -Be $null

                 $service.Name | Should -Be $resourceParameters.Name
                 $serviceCimInstance.Name | Should -Be $resourceParameters.Name
            }

            It 'Should have edited the service to have the specified startup account name' {
                $serviceCimInstance.StartName | Should -Be 'NT Authority\LocalService'
            }

            It 'Should return true from Test-TargetResource with the same parameters' {
                MSFT_xServiceResource\Test-TargetResource @resourceParameters | Should -Be $true
            }
        }

        Context 'Remove the service' {
            Reset-DSC

            $configurationName = 'TestCreateService'
            $resourceParameters = @{
                Name = $script:existingServiceProperties.Name
                Path = $script:existingServiceProperties.Path
                Ensure = 'Absent'
            }

            It 'Should compile and apply the MOF without throwing' {
                {
                    . $script:configurationAllExceptCredentialFilePath -ConfigurationName $configurationName
                    & $configurationName -OutputPath $TestDrive @resourceParameters
                    Start-DscConfiguration -Path $TestDrive -ErrorAction 'Stop' -Wait -Force
                } | Should -Not -Throw
            }

            $service = Get-Service -Name $resourceParameters.Name -ErrorAction 'SilentlyContinue'
            $serviceCimInstance = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name='$($resourceParameters.Name)'" -ErrorAction 'SilentlyContinue'

            It 'Should have removed the service with specified name' {
                 $service | Should -Be $null
                 $serviceCimInstance | Should -Be $null
            }

            It 'Should return true from Test-TargetResource with the same parameters' {
                MSFT_xServiceResource\Test-TargetResource @resourceParameters | Should -Be $true
            }
        }
    }
}
finally
{
    Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment
}