Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
322dafb
Add integration tests for Complete-SqlDscImage command
johlju Oct 18, 2025
dff6cbf
Improve commands and tests
johlju Oct 18, 2025
d25bc21
Add integration tests for TempDB file count and server collation in I…
johlju Oct 19, 2025
c61024c
Add InstanceId parameter to Complete-SqlDscImage integration tests
johlju Oct 19, 2025
ab14724
Make InstanceId parameter mandatory in Complete-SqlDscImage and updat…
johlju Oct 19, 2025
216a773
Update examples in Invoke-SetupAction and Complete-SqlDscImage to inc…
johlju Oct 19, 2025
d478c93
Throw exceptions instead of writing errors when SQL Server service fa…
johlju Oct 19, 2025
a0156d8
Improve verbose logging in integration tests to include computer name
johlju Oct 19, 2025
d93d467
Fix formatting of ProductKey and update regex for InstanceName in Inv…
johlju Oct 19, 2025
0613849
Add mock parameter for InstallSharedWOWDir in Install-SqlDscServer tests
johlju Oct 19, 2025
65ffc28
Add mock parameter for InstallSharedWOWDir in Invoke-SetupAction tests
johlju Oct 19, 2025
ad84bfb
Add suppression for PSAvoidUsingConvertToSecureStringWithPlainText in…
johlju Oct 19, 2025
9c52ddd
Add dependency for Integration_Test_Commands_SqlServer_PreparedImage …
johlju Oct 19, 2025
55b090e
Refactor credential variable names for clarity in Initialize-SqlDscRe…
johlju Oct 19, 2025
b9b2000
Update Invoke-SetupAction tests to suppress output from Invoke-SetupA…
johlju Oct 19, 2025
c79d124
Remove unused mock parameter 'InstanceId' from Complete-SqlDscImage t…
johlju Oct 19, 2025
1721133
Rename test description for error handling when ConfigurationFile doe…
johlju Oct 19, 2025
227dfb3
Update Invoke-SetupAction and Complete-SqlDscImage to require SqlSvcA…
johlju Oct 19, 2025
b39e129
Add AgtSvcAccount parameter to Invoke-SetupAction and Complete-SqlDsc…
johlju Oct 19, 2025
d713df4
Refactor Complete-SqlDscImage integration tests to streamline service…
johlju Oct 19, 2025
f045395
Remove mandatory requirement for InstanceId, AgtSvcAccount, and SqlSv…
johlju Oct 19, 2025
3a4f700
Update Assert-SetupActionProperties to require InstanceId, SqlSvcAcco…
johlju Oct 19, 2025
acf9003
Add mandatory InstanceId requirement for PrepareImage setup action in…
johlju Oct 19, 2025
b034017
Remove mandatory requirement for InstanceId in PrepareImage parameter…
johlju Oct 19, 2025
95a8d75
Add Remove-SqlServerFromCIImage script to clean pre-installed SQL Ser…
johlju Oct 19, 2025
f02088a
Fix typos in README for Remove-SqlServerFromCIImage script
johlju Oct 20, 2025
ae80547
Refactor Remove-SqlServerFromCIImage script to improve parameter hand…
johlju Oct 20, 2025
8eeb7a2
Update Remove-SqlServerFromCIImage script to enhance Force parameter …
johlju Oct 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added script `Remove-SqlServerFromCIImage.ps1` to remove pre-installed SQL Server
components from Microsoft Hosted agents that conflict with PrepareImage operations.
The script is now run automatically in the CI pipeline before PrepareImage tests
to resolve Sysprep compatibility errors [issue #2212](https://github.com/dsccommunity/SqlServerDsc/issues/2212).
- Added integration tests for `Complete-SqlDscImage` command to ensure command
reliability in prepared image installation workflows. The test runs in a separate
pipeline job `Integration_Test_Commands_SqlServer_PreparedImage` with its own CI
worker, and verifies the completion of SQL Server instances prepared using
`Install-SqlDscServer` with the `-PrepareImage` parameter. The test includes
scenarios with minimal parameters and various service account/directory
configurations [issue #2212](https://github.com/dsccommunity/SqlServerDsc/issues/2212).
- Added integration test for `Install-SqlDscServer` with the `-PrepareImage`
parameter set to support the prepared image installation workflow. This test
(`Install-SqlDscServer.Integration.PrepareImage.Tests.ps1`) runs in the
`Integration_Test_Commands_SqlServer_PreparedImage` pipeline job and prepares
a DSCSQLTEST instance that is later completed by `Complete-SqlDscImage` tests
[issue #2212](https://github.com/dsccommunity/SqlServerDsc/issues/2212).
- Added integration tests for `Initialize-SqlDscRebuildDatabase` command to ensure
command reliability. The test runs in group 8, alongside `Repair-SqlDscServer`,
to verify the rebuild database functionality on the DSCSQLTEST instance
Expand Down
76 changes: 76 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,81 @@ stages:
testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml'
testRunTitle: 'Integration Commands ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))'

- stage: Integration_Test_Commands_SqlServer_PreparedImage
displayName: 'Integration Test Commands - SQL Server (Prepared Image)'
dependsOn: Quality_Test_and_Unit_Test
jobs:
- job: Test_Integration_PreparedImage
displayName: 'Commands - Prepared Image'
strategy:
matrix:
SQL2017_WIN2022:
JOB_VMIMAGE: 'windows-2022'
TEST_CONFIGURATION: 'Integration_SQL2017'
SQL2019_WIN2022:
JOB_VMIMAGE: 'windows-2022'
TEST_CONFIGURATION: 'Integration_SQL2019'
SQL2019_WIN2025:
JOB_VMIMAGE: 'windows-2025'
TEST_CONFIGURATION: 'Integration_SQL2019'
SQL2022_WIN2022:
JOB_VMIMAGE: 'windows-2022'
TEST_CONFIGURATION: 'Integration_SQL2022'
SQL2022_WIN2025:
JOB_VMIMAGE: 'windows-2025'
TEST_CONFIGURATION: 'Integration_SQL2022'
pool:
vmImage: $(JOB_VMIMAGE)
timeoutInMinutes: '0'
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download Build Artifact'
inputs:
buildType: 'current'
artifactName: $(buildArtifactName)
targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)'
- task: PowerShell@2
name: configureWinRM
displayName: 'Configure WinRM'
inputs:
targetType: 'inline'
script: 'winrm quickconfig -quiet'
pwsh: false
- powershell: |
Import-Module -Name ./tests/TestHelpers/CommonTestHelper.psm1
Remove-PowerShellModuleFromCI -Name @('SqlServer', 'SQLPS')
Remove-Module -Name CommonTestHelper
name: cleanCIWorker
displayName: 'Clean CI worker'
- powershell: |
Write-Information -MessageData 'Removing pre-installed SQL Server components that conflict with PrepareImage...' -InformationAction Continue
& ./tests/Integration/Commands/Scripts/Remove-SqlServerFromCIImage.ps1 -Confirm:$false -InformationAction Continue -Verbose
name: removeSqlServerComponents
displayName: 'Remove pre-installed SQL Server components'
- powershell: |
./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $(TEST_CONFIGURATION) -PesterPath @(
# Run the integration tests in a specific group order for prepared image workflow.
# Group 0
'tests/Integration/Commands/Prerequisites.Integration.Tests.ps1'
'tests/Integration/Commands/Save-SqlDscSqlServerMediaFile.Integration.Tests.ps1'
'tests/Integration/Commands/Import-SqlDscPreferredModule.Integration.Tests.ps1'
# Group 1
'tests/Integration/Commands/Install-SqlDscServer.Integration.PrepareImage.Tests.ps1'
# Group 2
'tests/Integration/Commands/Complete-SqlDscImage.Integration.Tests.ps1'
# Group 9
'tests/Integration/Commands/Uninstall-SqlDscServer.Integration.Tests.ps1'
)
name: test
displayName: 'Run Integration Test'
- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: succeededOrFailed()
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml'
testRunTitle: 'Integration Commands - Prepared Image ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))'

- stage: Integration_Test_Commands_ReportingServices
displayName: 'Integration Test Commands - Reporting Services'
dependsOn: Integration_Test_Commands_SqlServer
Expand Down Expand Up @@ -924,6 +999,7 @@ stages:
dependsOn:
- Quality_Test_and_Unit_Test
- Integration_Test_Commands_SqlServer
- Integration_Test_Commands_SqlServer_PreparedImage
- Integration_Test_Commands_ReportingServices
- Integration_Test_Commands_BIReportServer
- Integration_Test_Resources_SqlServer
Expand Down
29 changes: 25 additions & 4 deletions source/Private/Assert-SetupActionProperties.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,38 @@ function Assert-SetupActionProperties
)
}

# If feature is SQLENGINE, then for specified setup actions the parameter AgtSvcAccount is mandatory.
if ($SetupAction -in ('CompleteImage', 'InstallFailoverCluster', 'PrepareFailoverCluster', 'AddNode'))
if ($SetupAction -in @('CompleteImage'))
{
# If feature is SQLENGINE, then InstanceId, SqlSvcAccount and AgtSvcAccount are mandatory.
if ($Property.ContainsKey('Features') -and $Property.Features -contains 'SQLENGINE')
{
Assert-BoundParameter -BoundParameterList $Property -RequiredParameter @('AgtSvcAccount')
Assert-BoundParameter -BoundParameterList $Property -RequiredParameter @(
'InstanceId'
'SqlSvcAccount'
'AgtSvcAccount'
)
}
}

if ($SetupAction -in @('PrepareImage'))
{
# If feature is SQLENGINE, then InstanceId is mandatory.
if ($Property.ContainsKey('Features') -and $Property.Features -contains 'SQLENGINE')
{
Assert-BoundParameter -BoundParameterList $Property -RequiredParameter @(
'InstanceId'
)
}
}

if ($SetupAction -in ('InstallFailoverCluster', 'PrepareFailoverCluster', 'AddNode'))
if ($SetupAction -in @('InstallFailoverCluster', 'PrepareFailoverCluster', 'AddNode'))
{
# If feature is SQLENGINE, then for specified setup actions the parameter AgtSvcAccount is mandatory.
if ($Property.ContainsKey('Features') -and $Property.Features -contains 'SQLENGINE')
{
Assert-BoundParameter -BoundParameterList $Property -RequiredParameter @('AgtSvcAccount')
}

# The parameter ASSvcAccount is mandatory if feature AS is installed and setup action is InstallFailoverCluster, PrepareFailoverCluster, or AddNode.
if ($Property.ContainsKey('Features') -and $Property.Features -contains 'AS')
{
Expand Down
5 changes: 3 additions & 2 deletions source/Private/Invoke-SetupAction.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@
Prepares the server for using the database engine for an instance named 'MyInstance'.

.EXAMPLE
Invoke-SetupAction -CompleteImage -AcceptLicensingTerms -MediaPath 'E:\'
Invoke-SetupAction -CompleteImage -AcceptLicensingTerms -InstanceId 'MSSQLSERVER' -SqlSvcAccount 'NT Service\MSSQLSERVER' -AgtSvcAccount 'NT Service\MSSQLSERVER' -MediaPath 'E:\'

Completes install on a server that was previously prepared (by using prepare image).

Expand Down Expand Up @@ -694,6 +694,7 @@ function Invoke-SetupAction

[Parameter(ParameterSetName = 'Install')]
[Parameter(ParameterSetName = 'InstallRole')]
[Parameter(ParameterSetName = 'PrepareImage')]
[Parameter(ParameterSetName = 'InstallFailoverCluster')]
[Parameter(ParameterSetName = 'PrepareFailoverCluster')]
[System.String]
Expand All @@ -710,7 +711,7 @@ function Invoke-SetupAction

[Parameter(ParameterSetName = 'Install')]
[Parameter(ParameterSetName = 'InstallRole')]
[Parameter(ParameterSetName = 'PrepareImage', Mandatory = $true)]
[Parameter(ParameterSetName = 'PrepareImage')]
[Parameter(ParameterSetName = 'CompleteImage')]
[Parameter(ParameterSetName = 'Upgrade')]
[Parameter(ParameterSetName = 'InstallFailoverCluster')]
Expand Down
2 changes: 1 addition & 1 deletion source/Public/Complete-SqlDscImage.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
None.

.EXAMPLE
Complete-SqlDscImage -AcceptLicensingTerms -MediaPath 'E:\'
Complete-SqlDscImage -AcceptLicensingTerms -MediaPath 'E:\' -InstanceId 'MSSQLSERVER' -SqlSvcAccount 'NT Service\MSSQLSERVER' -AgtSvcAccount 'NT Service\MSSQLSERVER'

Completes the image installation of the SQL Server default instance that
was prepared using `Install-SqlDscServer` with the parameter `-PrepareImage`.
Expand Down
3 changes: 2 additions & 1 deletion source/Public/Install-SqlDscServer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ function Install-SqlDscServer

[Parameter(ParameterSetName = 'Install')]
[Parameter(ParameterSetName = 'InstallRole')]
[Parameter(ParameterSetName = 'PrepareImage')]
[Parameter(ParameterSetName = 'InstallFailoverCluster')]
[Parameter(ParameterSetName = 'PrepareFailoverCluster')]
[System.String]
Expand All @@ -583,7 +584,7 @@ function Install-SqlDscServer

[Parameter(ParameterSetName = 'Install')]
[Parameter(ParameterSetName = 'InstallRole')]
[Parameter(ParameterSetName = 'PrepareImage', Mandatory = $true)]
[Parameter(ParameterSetName = 'PrepareImage')]
[Parameter(ParameterSetName = 'Upgrade')]
[Parameter(ParameterSetName = 'InstallFailoverCluster')]
[Parameter(ParameterSetName = 'PrepareFailoverCluster')]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Plain text used only for test credentials in CI.')]
param ()

BeforeDiscovery {
try
{
if (-not (Get-Module -Name 'DscResource.Test'))
{
# Assumes dependencies have been resolved, so if this module is not available, run 'noop' task.
if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable))
{
# Redirect all streams to $null, except the error stream (stream 2)
& "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null
}

# If the dependencies have not been resolved, this will throw an error.
Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop'
}
}
catch [System.IO.FileNotFoundException]
{
throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks noop" first.'
}
}

BeforeAll {
$script:moduleName = 'SqlServerDsc'

Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop'
}

# cSpell: ignore DSCSQLTEST, PrepareImage, CompleteImage
Describe 'Complete-SqlDscImage' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') {
BeforeAll {
Write-Verbose -Message ('Running integration test as user ''{0}''.' -f $env:UserName) -Verbose

$computerName = Get-ComputerName
}

Context 'When completing a prepared SQL Server image' {
It 'Should run the command without throwing' {
# Set splatting parameters for Complete-SqlDscImage with service account configuration
$completeSqlDscImageParameters = @{
AcceptLicensingTerms = $true
InstanceName = 'DSCSQLTEST'
InstanceId = 'DSCSQLTEST'
SqlSvcAccount = '{0}\svc-SqlPrimary' -f $computerName
SqlSvcPassword = ConvertTo-SecureString -String 'yig-C^Equ3' -AsPlainText -Force
SqlSvcStartupType = 'Automatic'
AgtSvcAccount = '{0}\svc-SqlAgentPri' -f $computerName
AgtSvcPassword = ConvertTo-SecureString -String 'yig-C^Equ3' -AsPlainText -Force
AgtSvcStartupType = 'Automatic'
BrowserSvcStartupType = 'Automatic'
SecurityMode = 'SQL'
SAPwd = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force
SqlSysAdminAccounts = @(
('{0}\SqlAdmin' -f $computerName)
)
InstallSqlDataDir = 'C:\Program Files\Microsoft SQL Server\MSSQL\Data'
SqlBackupDir = 'C:\Program Files\Microsoft SQL Server\MSSQL\Backup'
SqlTempDbDir = 'C:\Program Files\Microsoft SQL Server\MSSQL\Data'
SqlTempDbLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL\Data'
SqlUserDbDir = 'C:\Program Files\Microsoft SQL Server\MSSQL\Data'
SqlUserDbLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL\Data'
TcpEnabled = $true
NpEnabled = $false
MediaPath = $env:IsoDrivePath
Verbose = $true
ErrorAction = 'Stop'
Force = $true
}

try
{
$null = Complete-SqlDscImage @completeSqlDscImageParameters
}
catch
{
# Output Summary.txt if it exists to help diagnose the failure
$summaryFiles = Get-ChildItem -Path 'C:\Program Files\Microsoft SQL Server' -Filter 'Summary.txt' -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\Setup Bootstrap\\Log\\' } |
Sort-Object -Property LastWriteTime -Descending |
Select-Object -First 1

if ($summaryFiles)
{
Write-Verbose "==== SQL Server Setup Summary.txt (from $($summaryFiles.FullName)) ====" -Verbose
Get-Content -Path $summaryFiles.FullName | Write-Verbose -Verbose
Write-Verbose "==== End of Summary.txt ====" -Verbose
}
else
{
Write-Verbose 'No Summary.txt file found.' -Verbose
}

# Re-throw the original error
throw $_
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Describe 'Initialize-SqlDscRebuildDatabase' -Tag @('Integration_SQL2017', 'Integ
$sqlService = Get-Service -Name $serviceName -ErrorAction 'Stop'
if ($sqlService.Status -ne 'Stopped')
{
Write-Error -Message "Failed to stop SQL Server service '$serviceName'"
throw "Failed to stop SQL Server service '$serviceName'"
}
}

Expand All @@ -70,7 +70,7 @@ Describe 'Initialize-SqlDscRebuildDatabase' -Tag @('Integration_SQL2017', 'Integ
$sqlService = Get-Service -Name $serviceName -ErrorAction 'Stop'
if ($sqlService.Status -ne 'Running')
{
Write-Error -Message "Failed to start SQL Server service '$serviceName'"
throw "Failed to start SQL Server service '$serviceName'"
}
}

Expand Down Expand Up @@ -202,6 +202,20 @@ Describe 'Initialize-SqlDscRebuildDatabase' -Tag @('Integration_SQL2017', 'Integ
$sqlService = Get-Service -Name $serviceName -ErrorAction 'Stop'
$sqlService.Status | Should -Be 'Running'
}

It 'Should have the requested TempDB file count' {
$mockSqlAdministratorUserName = 'SqlAdmin'
$mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force

$mockSqlAdministratorCredential = [System.Management.Automation.PSCredential]::new(
$mockSqlAdministratorUserName,
$mockSqlAdministratorPassword
)

$server = Connect-SqlDscDatabaseEngine -InstanceName 'DSCSQLTEST' -Credential $mockSqlAdministratorCredential -ErrorAction 'Stop'
$server.Databases['tempdb'].FileGroups['PRIMARY'].Files.Count | Should -Be 8
Disconnect-SqlDscDatabaseEngine -ServerObject $server -ErrorAction 'Stop'
}
}

Context 'When specifying optional SqlCollation parameter' {
Expand Down Expand Up @@ -258,6 +272,20 @@ Describe 'Initialize-SqlDscRebuildDatabase' -Tag @('Integration_SQL2017', 'Integ
$sqlService = Get-Service -Name $serviceName -ErrorAction 'Stop'
$sqlService.Status | Should -Be 'Running'
}

It 'Should set the requested server collation' {
$mockSqlAdministratorUserName = 'SqlAdmin'
$mockSqlAdministratorPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force

$mockSqlAdminCredential = [System.Management.Automation.PSCredential]::new(
$mockSqlAdministratorUserName,
$mockSqlAdministratorPassword
)

$server = Connect-SqlDscDatabaseEngine -InstanceName 'DSCSQLTEST' -Credential $mockSqlAdminCredential -ErrorAction 'Stop'
$server.Collation | Should -Be 'SQL_Latin1_General_CP1_CI_AS'
Disconnect-SqlDscDatabaseEngine -ServerObject $server -ErrorAction 'Stop'
}
}
}
}
Loading
Loading