diff --git a/CHANGELOG.md b/CHANGELOG.md index 1671343b0c..35d6657199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b7a3a94d18..391a9b7a0e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -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 @@ -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 diff --git a/source/Private/Assert-SetupActionProperties.ps1 b/source/Private/Assert-SetupActionProperties.ps1 index 96e223eb26..dd005ef7a0 100644 --- a/source/Private/Assert-SetupActionProperties.ps1 +++ b/source/Private/Assert-SetupActionProperties.ps1 @@ -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') { diff --git a/source/Private/Invoke-SetupAction.ps1 b/source/Private/Invoke-SetupAction.ps1 index be6ba2f5e3..c0084d6a70 100644 --- a/source/Private/Invoke-SetupAction.ps1 +++ b/source/Private/Invoke-SetupAction.ps1 @@ -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). @@ -694,6 +694,7 @@ function Invoke-SetupAction [Parameter(ParameterSetName = 'Install')] [Parameter(ParameterSetName = 'InstallRole')] + [Parameter(ParameterSetName = 'PrepareImage')] [Parameter(ParameterSetName = 'InstallFailoverCluster')] [Parameter(ParameterSetName = 'PrepareFailoverCluster')] [System.String] @@ -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')] diff --git a/source/Public/Complete-SqlDscImage.ps1 b/source/Public/Complete-SqlDscImage.ps1 index 2c37dffd8e..5873d3eaab 100644 --- a/source/Public/Complete-SqlDscImage.ps1 +++ b/source/Public/Complete-SqlDscImage.ps1 @@ -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`. diff --git a/source/Public/Install-SqlDscServer.ps1 b/source/Public/Install-SqlDscServer.ps1 index 19e723136b..5c79ea5a4c 100644 --- a/source/Public/Install-SqlDscServer.ps1 +++ b/source/Public/Install-SqlDscServer.ps1 @@ -567,6 +567,7 @@ function Install-SqlDscServer [Parameter(ParameterSetName = 'Install')] [Parameter(ParameterSetName = 'InstallRole')] + [Parameter(ParameterSetName = 'PrepareImage')] [Parameter(ParameterSetName = 'InstallFailoverCluster')] [Parameter(ParameterSetName = 'PrepareFailoverCluster')] [System.String] @@ -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')] diff --git a/tests/Integration/Commands/Complete-SqlDscImage.Integration.Tests.ps1 b/tests/Integration/Commands/Complete-SqlDscImage.Integration.Tests.ps1 new file mode 100644 index 0000000000..6c1840e895 --- /dev/null +++ b/tests/Integration/Commands/Complete-SqlDscImage.Integration.Tests.ps1 @@ -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 $_ + } + } + } +} diff --git a/tests/Integration/Commands/Initialize-SqlDscRebuildDatabase.Integration.Tests.ps1 b/tests/Integration/Commands/Initialize-SqlDscRebuildDatabase.Integration.Tests.ps1 index a11ff9c202..c7c4891452 100644 --- a/tests/Integration/Commands/Initialize-SqlDscRebuildDatabase.Integration.Tests.ps1 +++ b/tests/Integration/Commands/Initialize-SqlDscRebuildDatabase.Integration.Tests.ps1 @@ -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'" } } @@ -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'" } } @@ -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' { @@ -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' + } } } } diff --git a/tests/Integration/Commands/Install-SqlDscServer.Integration.PrepareImage.Tests.ps1 b/tests/Integration/Commands/Install-SqlDscServer.Integration.PrepareImage.Tests.ps1 new file mode 100644 index 0000000000..494e088e28 --- /dev/null +++ b/tests/Integration/Commands/Install-SqlDscServer.Integration.PrepareImage.Tests.ps1 @@ -0,0 +1,85 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +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 SQLSERVERAGENT, DSCSQLTEST, PrepareImage +Describe 'Install-SqlDscServer - PrepareImage' -Tag @('Integration_SQL2017', 'Integration_SQL2019', 'Integration_SQL2022') { + BeforeAll { + $computerName = Get-ComputerName + Write-Verbose -Message ("Running integration test as user '{0}' on computer '{1}'." -f $env:UserName, $computerName) -Verbose + } + + Context 'When using PrepareImage parameter set' { + Context 'When preparing database engine instance for image' { + It 'Should run the command without throwing' { + # Set splatting parameters for Install-SqlDscServer with PrepareImage + $installSqlDscServerParameters = @{ + PrepareImage = $true + AcceptLicensingTerms = $true + InstanceId = 'DSCSQLTEST' + Features = 'SQLENGINE' + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' + MediaPath = $env:IsoDrivePath + Verbose = $true + ErrorAction = 'Stop' + Force = $true + } + + try + { + $null = Install-SqlDscServer @installSqlDscServerParameters + } + 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 $_ + } + } + } + } +} diff --git a/tests/Integration/Commands/README.md b/tests/Integration/Commands/README.md index 87827829d3..2be2b2b840 100644 --- a/tests/Integration/Commands/README.md +++ b/tests/Integration/Commands/README.md @@ -125,6 +125,23 @@ Initialize-SqlDscRebuildDatabase | 8 | 1 (Install-SqlDscServer) | DSCSQLTEST | - Uninstall-SqlDscServer | 9 | 8 (Repair-SqlDscServer), 8 (Initialize-SqlDscRebuildDatabase) | - | - +### Integration_Test_Commands_SqlServer_PreparedImage + +Tests for SQL Server Database Engine commands using the prepared image installation +workflow. This test suite runs in a separate job with its own CI worker, testing +`Install-SqlDscServer` with `-PrepareImage` followed by `Complete-SqlDscImage`. + + +Command | Run order # | Depends on # | Use instance | Creates persistent objects +--- | --- | --- | --- | --- +Prerequisites | 0 | - | - | Sets up dependencies +Save-SqlDscSqlServerMediaFile | 0 | - | - | Downloads SQL Server media files +Import-SqlDscPreferredModule | 0 | - | - | - +Install-SqlDscServer (PrepareImage) | 1 | 0 (Prerequisites) | - | DSCSQLTEST instance prepared +Complete-SqlDscImage | 2 | 1 (Install-SqlDscServer) | DSCSQLTEST | Completes prepared image installation +Uninstall-SqlDscServer | 9 | 2 (Complete-SqlDscImage) | - | - + + ### Integration_Test_Commands_ReportingServices Tests for SQL Server Reporting Services commands. diff --git a/tests/Integration/Commands/Scripts/README.Remove-SqlServerFromCIImage.md b/tests/Integration/Commands/Scripts/README.Remove-SqlServerFromCIImage.md new file mode 100644 index 0000000000..1617db48bf --- /dev/null +++ b/tests/Integration/Commands/Scripts/README.Remove-SqlServerFromCIImage.md @@ -0,0 +1,124 @@ +# Remove-SqlServerFromCIImage.ps1 + +## Overview + +This script removes all pre-installed SQL Server components from Microsoft +Hosted CI agents that may conflict with SQL Server Sysprep/PrepareImage +operations. + +## Problem Statement + +Microsoft Hosted agents (both `windows-2022` and `windows-2025` images) come +with pre-installed SQL Server components such as: + +- SQL Server LocalDB +- SQL Server Client Tools +- SQL Server Shared Components +- SQL Server ODBC Drivers +- SQL Server OLE DB Providers +- SQL Server Native Client + +These pre-installed components are incompatible with SQL Server +PrepareImage/Sysprep operations, causing setup to fail with the error: + +> Setup has detected that there are SQL Server features already installed on +> this machine that do not support Sysprep. + +## Solution + +This script identifies and removes all SQL Server-related products from the +system registry before running PrepareImage operations. It: + +1. Scans the Windows registry for SQL Server-related products +1. Identifies uninstall methods (MSI or custom uninstaller) +1. Removes each product silently +1. Provides detailed logging of the removal process + +## Usage + +### In CI Pipeline + +The script is automatically run in the Azure Pipelines CI before PrepareImage +tests: + +```yaml +- powershell: | + Write-Information -MessageData 'Removing pre-installed SQL Server + components that conflict with PrepareImage...' -InformationAction Continue + & ./tests/Integration/Commands/Remove-SqlServerFromCIImage.ps1 + -Confirm:$false -InformationAction Continue -Verbose + name: removeSqlServerComponents + displayName: 'Remove pre-installed SQL Server components' +``` + +### Manual Usage + +To run the script manually on a CI agent or local machine: + +```powershell +# Remove all SQL Server components without prompting +.\Remove-SqlServerFromCIImage.ps1 -Confirm:$false + +# Preview what would be removed without actually removing +.\Remove-SqlServerFromCIImage.ps1 -WhatIf + +# Run with verbose output +.\Remove-SqlServerFromCIImage.ps1 -Verbose -Confirm:$false +``` + +## How It Works + +1. **Registry Scan**: Searches both 32-bit and 64-bit registry uninstall + locations +1. **Pattern Matching**: Uses multiple patterns to identify SQL Server-related + products +1. **Uninstall Method Detection**: Automatically detects whether to use: + - `msiexec.exe` for MSI-based products + - Custom uninstaller for EXE-based products +1. **Silent Uninstall**: Runs all uninstallers with quiet/silent flags to avoid + prompts +1. **Logging**: Creates detailed logs in `%TEMP%` for troubleshooting + +## Exit Codes + +- **0**: All products removed successfully (or no products found) +- **Non-zero**: One or more products failed to uninstall (warnings are logged) + +## Products Removed + +The script removes products matching these patterns: + +- `*SQL Server*LocalDB*` +- `*SQL Server*Client*` +- `*SQL Server*Shared*` +- `*SQL Server*Browser*` +- `*SQL Server*Management*` +- `*SQL Server*Tools*` +- `*SQL Server*Native*Client*` +- `*SQL Server*ODBC*` +- `*SQL Server*OLE*DB*` +- `*SQL Server*T-SQL*` +- `*SQL Server*Command*Line*Utilities*` +- `*SQL Server*Data*Tier*` +- `*Microsoft*ODBC*Driver*SQL*Server*` +- `*Microsoft*OLE*DB*Driver*SQL*Server*` + +## Safety + +- Uses `SupportsShouldProcess` with `-Confirm` and `-WhatIf` support +- Validates uninstall methods before execution +- Logs all actions for audit trail +- Handles errors gracefully without stopping the entire process + +## Notes + +- A system restart may be required after removal for changes to take full effect +- The script does NOT remove SQL Server instances installed by the tests + themselves +- Only removes pre-installed components that ship with the Microsoft Hosted + agent images + +## Related Issues + +- [Issue #2212](https://github.com/dsccommunity/SqlServerDsc/issues/2212): + PrepareImage support and CI integration diff --git a/tests/Integration/Commands/Scripts/Remove-SqlServerFromCIImage.ps1 b/tests/Integration/Commands/Scripts/Remove-SqlServerFromCIImage.ps1 new file mode 100644 index 0000000000..037a668ebb --- /dev/null +++ b/tests/Integration/Commands/Scripts/Remove-SqlServerFromCIImage.ps1 @@ -0,0 +1,272 @@ +<# + .SYNOPSIS + Removes all pre-installed SQL Server components from Microsoft Hosted CI agents. + + .DESCRIPTION + This script removes all SQL Server related products that may be pre-installed + on Microsoft Hosted agents. These pre-installed components can conflict with + SQL Server Sysprep/PrepareImage operations, causing errors like "Setup has + detected that there are SQL Server features already installed on this machine + that do not support Sysprep." + + The script identifies and uninstalls: + - SQL Server LocalDB + - SQL Server Client Tools + - SQL Server Shared Components + - SQL Server Browser + - Any other SQL Server related products + + .PARAMETER Force + Suppresses confirmation prompts before removing each product. + + .EXAMPLE + .\Remove-SqlServerFromCIImage.ps1 + + Removes all pre-installed SQL Server components from the CI agent. + + .EXAMPLE + .\Remove-SqlServerFromCIImage.ps1 -WhatIf + + Shows what SQL Server components would be removed without actually removing them. + + .EXAMPLE + .\Remove-SqlServerFromCIImage.ps1 -Force + + Removes all pre-installed SQL Server components from the CI agent, without prompting + for confirmation. + + .NOTES + This script should be run on Microsoft Hosted agents before performing + SQL Server PrepareImage operations. +#> +[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] +param +( + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force +) + +$ErrorActionPreference = 'Stop' + +if ($Force.IsPresent -and -not $Confirm) +{ + $ConfirmPreference = 'None' +} + +Write-Information -MessageData 'Starting removal of pre-installed SQL Server components from CI image...' -InformationAction 'Continue' + +# Registry paths to check for installed products +$uninstallKeys = @( + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', + 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' +) + +Write-Verbose -Message 'Scanning registry for SQL Server products...' + +$allProducts = Get-ItemProperty -Path $uninstallKeys -ErrorAction 'SilentlyContinue' + +# Define patterns to match SQL Server related products +$sqlServerPatterns = @( + '*SQL Server*LocalDB*', + '*SQL Server*Client*', + '*SQL Server*Shared*', + '*SQL Server*Browser*', + '*SQL Server*Management*', + '*SQL Server*Tools*', + '*SQL Server*Native*Client*', + '*SQL Server*ODBC*', + '*SQL Server*OLE*DB*', + '*SQL Server*T-SQL*', + '*SQL Server*Command*Line*Utilities*', + '*SQL Server*Data*Tier*', + '*Microsoft*ODBC*Driver*SQL*Server*', + '*Microsoft*OLE*DB*Driver*SQL*Server*' +) + +# Find all SQL Server related products +$sqlServerProducts = $allProducts | Where-Object { + $displayName = $_.DisplayName + + if (-not $displayName) + { + return $false + } + + foreach ($pattern in $sqlServerPatterns) + { + if ($displayName -like $pattern) + { + return $true + } + } + + return $false +} + +if (-not $sqlServerProducts) +{ + Write-Information -MessageData 'No pre-installed SQL Server components found on this CI image.' -InformationAction 'Continue' + + return +} + +Write-Information -MessageData ('Found {0} SQL Server related product(s) to remove:' -f $sqlServerProducts.Count) -InformationAction 'Continue' + +foreach ($product in $sqlServerProducts) +{ + Write-Information -MessageData (' - {0} (Version: {1})' -f $product.DisplayName, $product.DisplayVersion) -InformationAction 'Continue' +} + +Write-Information -MessageData '' -InformationAction 'Continue' + +$removedCount = 0 +$failedCount = 0 +$skippedCount = 0 + +foreach ($product in $sqlServerProducts) +{ + $productName = $product.DisplayName + $productCode = $product.PSChildName + $uninstallString = $product.UninstallString + + Write-Verbose -Message ('Processing: {0}' -f $productName) + + # Skip if no uninstall information is available + if (-not $productCode -and -not $uninstallString) + { + Write-Warning -Message ('Skipping {0}: No uninstall information found.' -f $productName) + $skippedCount++ + + continue + } + + $descriptionMessage = 'Removing SQL Server component ''{0}'' with product code ''{1}'' from the CI image.' -f $productName, $productCode + $confirmationMessage = 'Are you sure you want to remove ''{0}''?' -f $productName + $captionMessage = 'Removing SQL Server component from CI image' + + if ($PSCmdlet.ShouldProcess($descriptionMessage, $confirmationMessage, $captionMessage)) + { + try + { + Write-Information -MessageData ('Uninstalling: {0}...' -f $productName) -InformationAction 'Continue' + + # Determine uninstall method based on product code format + if ($productCode -match '^\{[A-F0-9\-]+\}$') + { + # MSI product - use msiexec + $uninstallArgs = @( + '/x' + $productCode + '/qn' + '/norestart' + '/L*v' + (Join-Path -Path $env:TEMP -ChildPath ('SqlServerUninstall_{0}.log' -f $productCode)) + ) + + Write-Verbose -Message ('Using msiexec to uninstall product code: {0}' -f $productCode) + + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + + $process = Start-Process -FilePath 'msiexec.exe' -ArgumentList $uninstallArgs -Wait -PassThru -NoNewWindow + + $ErrorActionPreference = $previousErrorActionPreference + + if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) + { + Write-Information -MessageData ('Successfully uninstalled: {0}' -f $productName) -InformationAction 'Continue' + $removedCount++ + } + else + { + Write-Warning -Message ('Failed to uninstall {0}. Exit code: {1}' -f $productName, $process.ExitCode) + $failedCount++ + } + } + elseif ($uninstallString) + { + # Custom uninstaller + Write-Verbose -Message ('Using custom uninstaller: {0}' -f $uninstallString) + + # Parse uninstall string to separate executable and arguments + if ($uninstallString -match '^"([^"]+)"(.*)$') + { + $executable = $Matches[1] + $arguments = $Matches[2].Trim() + } + else + { + $executable = $uninstallString + $arguments = '' + } + + # Add silent flags if not already present + if ($arguments -notmatch '/quiet|/silent|/s\b|/qn') + { + $arguments += ' /quiet /norestart' + } + + Write-Verbose -Message ('Executable: {0}' -f $executable) + Write-Verbose -Message ('Arguments: {0}' -f $arguments) + + if (Test-Path -Path $executable) + { + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + + $process = Start-Process -FilePath $executable -ArgumentList $arguments -Wait -PassThru -NoNewWindow + + $ErrorActionPreference = $previousErrorActionPreference + + if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) + { + Write-Information -MessageData ('Successfully uninstalled: {0}' -f $productName) -InformationAction 'Continue' + $removedCount++ + } + else + { + Write-Warning -Message ('Failed to uninstall {0}. Exit code: {1}' -f $productName, $process.ExitCode) + $failedCount++ + } + } + else + { + Write-Warning -Message ('Uninstaller not found: {0}' -f $executable) + $failedCount++ + } + } + else + { + Write-Warning -Message ('Skipping {0}: Unable to determine uninstall method.' -f $productName) + $skippedCount++ + } + } + catch + { + Write-Warning -Message ('Error uninstalling {0}: {1}' -f $productName, $_.Exception.Message) + $failedCount++ + } + } + else + { + Write-Information -MessageData ('Skipped: {0}' -f $productName) -InformationAction 'Continue' + $skippedCount++ + } +} + +Write-Information -MessageData '' -InformationAction 'Continue' +Write-Information -MessageData 'SQL Server component removal summary:' -InformationAction 'Continue' +Write-Information -MessageData (' - Successfully removed: {0}' -f $removedCount) -InformationAction 'Continue' +Write-Information -MessageData (' - Failed to remove: {0}' -f $failedCount) -InformationAction 'Continue' +Write-Information -MessageData (' - Skipped: {0}' -f $skippedCount) -InformationAction 'Continue' + +if ($failedCount -gt 0) +{ + Write-Warning -Message 'Some SQL Server components could not be removed. This may cause issues with PrepareImage operations.' +} +elseif ($removedCount -gt 0) +{ + Write-Information -MessageData 'All SQL Server components were successfully removed.' -InformationAction 'Continue' + Write-Information -MessageData 'A system restart may be required for changes to take full effect.' -InformationAction 'Continue' +} diff --git a/tests/Unit/Private/Assert-SetupActionProperties.Tests.ps1 b/tests/Unit/Private/Assert-SetupActionProperties.Tests.ps1 index 99bb494790..01923a05d9 100644 --- a/tests/Unit/Private/Assert-SetupActionProperties.Tests.ps1 +++ b/tests/Unit/Private/Assert-SetupActionProperties.Tests.ps1 @@ -417,6 +417,21 @@ Describe 'Assert-SetupActionProperties' -Tag 'Private' { MockMissingParameterName = 'AgtSvcAccount' MockFeature = 'SQLENGINE' } + @{ + MockSetupAction = 'CompleteImage' + MockMissingParameterName = 'InstanceId' + MockFeature = 'SQLENGINE' + } + @{ + MockSetupAction = 'CompleteImage' + MockMissingParameterName = 'SqlSvcAccount' + MockFeature = 'SQLENGINE' + } + @{ + MockSetupAction = 'PrepareImage' + MockMissingParameterName = 'InstanceId' + MockFeature = 'SQLENGINE' + } @{ MockSetupAction = 'InstallFailoverCluster' MockMissingParameterName = 'AgtSvcAccount' diff --git a/tests/Unit/Private/Invoke-SetupAction.Tests.ps1 b/tests/Unit/Private/Invoke-SetupAction.Tests.ps1 index 65cae4712a..6c8491ac7a 100644 --- a/tests/Unit/Private/Invoke-SetupAction.Tests.ps1 +++ b/tests/Unit/Private/Invoke-SetupAction.Tests.ps1 @@ -77,7 +77,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { @{ MockParameterSetName = 'PrepareImage' # cSpell: disable-next - MockExpectedParameters = '-PrepareImage -AcceptLicensingTerms -MediaPath -Features -InstanceId [-IAcknowledgeEntCalLimits] [-Enu] [-UpdateEnabled] [-UpdateSource ] [-InstallSharedDir ] [-InstanceDir ] [-PBEngSvcAccount ] [-PBEngSvcPassword ] [-PBEngSvcStartupType ] [-PBStartPortRange ] [-PBEndPortRange ] [-PBScaleOut] [-Timeout ] [-Force] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-PrepareImage -AcceptLicensingTerms -MediaPath -Features [-IAcknowledgeEntCalLimits] [-Enu] [-UpdateEnabled] [-UpdateSource ] [-InstallSharedDir ] [-InstallSharedWowDir ] [-InstanceDir ] [-InstanceId ] [-PBEngSvcAccount ] [-PBEngSvcPassword ] [-PBEngSvcStartupType ] [-PBStartPortRange ] [-PBEndPortRange ] [-PBScaleOut] [-Timeout ] [-Force] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'CompleteImage' @@ -188,7 +188,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { } } - It 'Should throw an error when the MediaPath does not exist' { + It 'Should throw when ConfigurationFile does not exist' { InModuleScope -ScriptBlock { { Invoke-SetupAction @mockDefaultParameters } | Should -Throw -ExpectedMessage "Cannot validate argument on parameter 'ConfigurationFile'. The specified configuration file was not found." @@ -229,7 +229,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -248,7 +248,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -267,7 +267,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -299,7 +299,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly 'PBPORTRANGE=16450-16460' # cspell: disable-line @@ -738,7 +738,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -828,7 +828,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue # Redirect all verbose stream to $null to ge no output from ShouldProcess. - Invoke-SetupAction @installSqlDscServerParameters -Verbose 4> $null + $null = Invoke-SetupAction @installSqlDscServerParameters -Verbose 4> $null $mockVerboseMessage = $script:localizedData.Invoke_SetupAction_SetupArguments @@ -880,7 +880,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Upgrade' @@ -897,7 +897,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Upgrade' @@ -914,7 +914,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -1016,7 +1016,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -1070,7 +1070,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=InstallFailoverCluster' @@ -1092,7 +1092,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=InstallFailoverCluster' @@ -1114,7 +1114,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -1149,7 +1149,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly 'PBPORTRANGE=16450-16460' # cspell: disable-line @@ -1481,7 +1481,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -1526,7 +1526,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=PrepareFailoverCluster' @@ -1543,7 +1543,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=PrepareFailoverCluster' @@ -1560,7 +1560,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -1591,7 +1591,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly 'PBPORTRANGE=16450-16460' # cspell: disable-line @@ -1768,7 +1768,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -1819,7 +1819,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=CompleteFailoverCluster' @@ -1839,7 +1839,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=CompleteFailoverCluster' @@ -1859,7 +1859,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -2064,7 +2064,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -2113,7 +2113,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=AddNode' @@ -2130,7 +2130,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=AddNode' @@ -2147,7 +2147,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -2182,7 +2182,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly 'PBPORTRANGE=16450-16460' # cspell: disable-line @@ -2318,7 +2318,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -2360,7 +2360,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=RemoveNode' @@ -2376,7 +2376,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=RemoveNode' @@ -2392,7 +2392,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -2432,7 +2432,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -2480,7 +2480,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { # cspell: disable-next @@ -2496,7 +2496,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { # cspell: disable-next @@ -2512,7 +2512,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -2571,7 +2571,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -2614,7 +2614,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=RebuildDatabase' @@ -2631,7 +2631,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=RebuildDatabase' @@ -2648,7 +2648,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -2729,7 +2729,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -2773,7 +2773,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=EditionUpgrade' @@ -2791,7 +2791,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=EditionUpgrade' @@ -2809,7 +2809,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -2835,7 +2835,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { AcceptLicensingTerms = $true MediaPath = '\SqlMedia' InstanceName = 'INSTANCE' - ProductKey = 22222-00000-00000-00000-00000 + ProductKey = '22222-00000-00000-00000-00000' Force = $true } } @@ -2851,7 +2851,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -2893,7 +2893,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Repair' @@ -2909,7 +2909,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Repair' @@ -2925,7 +2925,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -2953,7 +2953,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly 'PBPORTRANGE=16450-16460' # cspell: disable-line @@ -3017,7 +3017,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -3062,7 +3062,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=PrepareImage' @@ -3080,7 +3080,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=PrepareImage' @@ -3098,7 +3098,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -3127,6 +3127,11 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { MockParameterValue = 'C:\Program Files\Microsoft SQL Server' MockExpectedRegEx = '\/INSTALLSHAREDDIR="C:\\Program Files\\Microsoft SQL Server"' # cspell: disable-line } + @{ + MockParameterName = 'InstallSharedWOWDir' + MockParameterValue = 'C:\Program Files (x86)\Microsoft SQL Server' + MockExpectedRegEx = '\/INSTALLSHAREDWOWDIR="C:\\Program Files \(x86\)\\Microsoft SQL Server"' # cspell: disable-line + } @{ MockParameterName = 'InstanceDir' MockParameterValue = 'C:\Program Files\Microsoft SQL Server' @@ -3186,7 +3191,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -3221,6 +3226,9 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { CompleteImage = $true AcceptLicensingTerms = $true MediaPath = '\SqlMedia' + InstanceId = 'MSSQLSERVER' + SqlSvcAccount = 'NT Service\MSSQLSERVER' + AgtSvcAccount = 'NT Service\MSSQLSERVER' } } } @@ -3228,10 +3236,11 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=CompleteImage' + $ArgumentList | Should -MatchExactly '\/INSTANCEID="MSSQLSERVER"' # Return $true if none of the above throw. $true @@ -3243,10 +3252,11 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=CompleteImage' + $ArgumentList | Should -MatchExactly '\/INSTANCEID="MSSQLSERVER"' # Return $true if none of the above throw. $true @@ -3258,7 +3268,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -3277,6 +3287,9 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { CompleteImage = $true AcceptLicensingTerms = $true MediaPath = '\SqlMedia' + InstanceId = 'MSSQLSERVER' + SqlSvcAccount = 'NT Service\MSSQLSERVER' + AgtSvcAccount = 'NT Service\MSSQLSERVER' Force = $true PBStartPortRange = 16450 PBEndPortRange = 16460 @@ -3286,7 +3299,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly 'PBPORTRANGE=16450-16460' # cspell: disable-line @@ -3302,7 +3315,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { @{ MockParameterName = 'InstanceName' MockParameterValue = 'INSTANCE' - MockExpectedRegEx = '\/INSTANCENAME="INSTANCE"*' # cspell: disable-line + MockExpectedRegEx = '\/INSTANCENAME="INSTANCE"' # cspell: disable-line } @{ MockParameterName = 'Enu' @@ -3500,6 +3513,9 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { CompleteImage = $true AcceptLicensingTerms = $true MediaPath = '\SqlMedia' + InstanceId = 'MSSQLSERVER' + SqlSvcAccount = 'NT Service\MSSQLSERVER' + AgtSvcAccount = 'NT Service\MSSQLSERVER' Force = $true } } @@ -3515,7 +3531,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -3559,7 +3575,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Uninstall' @@ -3576,7 +3592,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Uninstall' @@ -3593,7 +3609,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -3629,7 +3645,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { AzureRegion = 'West-US' AzureTenantId = '7e52fb9e-6aad-426c-98c4-7d2f11f7e94b' AzureServicePrincipal = 'MyServicePrincipal' - AzureServicePrincipalSecret = ('jT7ELPbD2GGuvLmjABDL' | ConvertTo-SecureString -AsPlainText -Force) # cspell: disable-linePbD2GGuvLmjABDL' | ConvertTo-SecureString -AsPlainText -Force) # cspell: disable-line + AzureServicePrincipalSecret = ('jT7ELPbD2GGuvLmjABDL' | ConvertTo-SecureString -AsPlainText -Force) # cspell: disable-line } } } @@ -3637,7 +3653,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -3660,7 +3676,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -3683,7 +3699,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -3729,7 +3745,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -3773,7 +3789,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -3790,7 +3806,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -3807,7 +3823,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -3863,7 +3879,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx @@ -3915,7 +3931,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue # Redirect all verbose stream to $null to ge no output from ShouldProcess. - Invoke-SetupAction @installSqlDscServerParameters -Verbose 4> $null + $null = Invoke-SetupAction @installSqlDscServerParameters -Verbose 4> $null $mockVerboseMessage = $script:localizedData.Invoke_SetupAction_SetupArguments @@ -3957,7 +3973,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Confirm with value $false' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Confirm:$false @mockDefaultParameters + $null = Invoke-SetupAction -Confirm:$false @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -3974,7 +3990,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter Force' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -Force @mockDefaultParameters + $null = Invoke-SetupAction -Force @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly '\/ACTION=Install' @@ -3991,7 +4007,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { Context 'When using parameter WhatIf' { It 'Should call the mock with the correct argument string' { InModuleScope -ScriptBlock { - Invoke-SetupAction -WhatIf @mockDefaultParameters + $null = Invoke-SetupAction -WhatIf @mockDefaultParameters Should -Invoke -CommandName Start-SqlSetupProcess -Exactly -Times 0 -Scope It } @@ -4037,7 +4053,7 @@ Describe 'Invoke-SetupAction' -Tag 'Private' { InModuleScope -Parameters $_ -ScriptBlock { $installSqlDscServerParameters.$MockParameterName = $MockParameterValue - Invoke-SetupAction @installSqlDscServerParameters + $null = Invoke-SetupAction @installSqlDscServerParameters Should -Invoke -CommandName Start-SqlSetupProcess -ParameterFilter { $ArgumentList | Should -MatchExactly $MockExpectedRegEx diff --git a/tests/Unit/Public/Complete-SqlDscImage.Tests.ps1 b/tests/Unit/Public/Complete-SqlDscImage.Tests.ps1 index ae0f08bb1d..2c3f3cbe5f 100644 --- a/tests/Unit/Public/Complete-SqlDscImage.Tests.ps1 +++ b/tests/Unit/Public/Complete-SqlDscImage.Tests.ps1 @@ -94,6 +94,9 @@ Describe 'Complete-SqlDscImage' -Tag 'Public' { $mockDefaultParameters = @{ AcceptLicensingTerms = $true MediaPath = '\SqlMedia' + InstanceId = 'MSSQLSERVER' + SqlSvcAccount = 'NT Service\MSSQLSERVER' + AgtSvcAccount = 'NT Service\MSSQLSERVER' ErrorAction = 'Stop' } } @@ -142,6 +145,9 @@ Describe 'Complete-SqlDscImage' -Tag 'Public' { $completeSqlDscImageParameters = @{ AcceptLicensingTerms = $true MediaPath = '\SqlMedia' + InstanceId = 'MSSQLSERVER' + SqlSvcAccount = 'NT Service\MSSQLSERVER' + AgtSvcAccount = 'NT Service\MSSQLSERVER' Force = $true PBStartPortRange = 16450 PBEndPortRange = 16460 @@ -171,11 +177,6 @@ Describe 'Complete-SqlDscImage' -Tag 'Public' { MockParameterValue = $true MockExpectedRegEx = '\/ENU\s*' } - @{ - MockParameterName = 'InstanceId' - MockParameterValue = 'Instance' - MockExpectedRegEx = '\/INSTANCEID="Instance"' # cspell: disable-line - } @{ MockParameterName = 'PBEngSvcAccount' MockParameterValue = 'NT Authority\NETWORK SERVICE' @@ -360,6 +361,9 @@ Describe 'Complete-SqlDscImage' -Tag 'Public' { $mockDefaultParameters = @{ AcceptLicensingTerms = $true MediaPath = '\SqlMedia' + InstanceId = 'MSSQLSERVER' + SqlSvcAccount = 'NT Service\MSSQLSERVER' + AgtSvcAccount = 'NT Service\MSSQLSERVER' Force = $true ErrorAction = 'Stop' } diff --git a/tests/Unit/Public/Install-SqlDscServer.Tests.ps1 b/tests/Unit/Public/Install-SqlDscServer.Tests.ps1 index ab1184d991..4d641e2ee1 100644 --- a/tests/Unit/Public/Install-SqlDscServer.Tests.ps1 +++ b/tests/Unit/Public/Install-SqlDscServer.Tests.ps1 @@ -72,7 +72,7 @@ Describe 'Install-SqlDscServer' -Tag 'Public' { @{ MockParameterSetName = 'PrepareImage' # cSpell: disable-next - MockExpectedParameters = '-PrepareImage -AcceptLicensingTerms -MediaPath -Features -InstanceId [-IAcknowledgeEntCalLimits] [-Enu] [-UpdateEnabled] [-UpdateSource ] [-InstallSharedDir ] [-InstanceDir ] [-PBEngSvcAccount ] [-PBEngSvcPassword ] [-PBEngSvcStartupType ] [-PBStartPortRange ] [-PBEndPortRange ] [-PBScaleOut] [-Timeout ] [-Force] [-WhatIf] [-Confirm] []' + MockExpectedParameters = '-PrepareImage -AcceptLicensingTerms -MediaPath -Features [-IAcknowledgeEntCalLimits] [-Enu] [-UpdateEnabled] [-UpdateSource ] [-InstallSharedDir ] [-InstallSharedWowDir ] [-InstanceDir ] [-InstanceId ] [-PBEngSvcAccount ] [-PBEngSvcPassword ] [-PBEngSvcStartupType ] [-PBStartPortRange ] [-PBEndPortRange ] [-PBScaleOut] [-Timeout ] [-Force] [-WhatIf] [-Confirm] []' } @{ MockParameterSetName = 'Upgrade' @@ -1869,6 +1869,11 @@ Describe 'Install-SqlDscServer' -Tag 'Public' { MockParameterValue = 'C:\Program Files\Microsoft SQL Server' MockExpectedRegEx = '\/INSTALLSHAREDDIR="C:\\Program Files\\Microsoft SQL Server"' # cspell: disable-line } + @{ + MockParameterName = 'InstallSharedWOWDir' + MockParameterValue = 'C:\Program Files (x86)\Microsoft SQL Server' + MockExpectedRegEx = '\/INSTALLSHAREDWOWDIR="C:\\Program Files \(x86\)\\Microsoft SQL Server"' # cspell: disable-line + } @{ MockParameterName = 'InstanceDir' MockParameterValue = 'C:\Program Files\Microsoft SQL Server'