From f13c0014c79ad8b848f5cd33bc81daa151810821 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Wed, 20 Mar 2019 15:38:06 +0100 Subject: [PATCH] SqlServerDsc: New resource SqlRSSetup, plus other changes (#1308) - Changes to SqlServerDsc - Added new resources. - SqlRSSetup - Added helper module DscResource.Common from the repository DscResource.Template. - Moved all helper functions from SqlServerDscHelper.psm1 to DscResource.Common. - Renamed Test-SqlDscParameterState to Test-DscParameterState. - Added helper module DscResource.LocalizationHelper from the repository DscResource.Template, this replaces the helper module CommonResourceHelper.psm1. - Cleaned up unit tests, mostly around loading cmdlet stubs and loading classes stubs, but also some tests that were using some odd variants. - Fix all integration tests according to issue [PowerShell/DscResource.Template#14](https://github.com/PowerShell/DscResource.Template/issues/14). - Changes to SqlSetup - Moved some resource specific helper functions to the new helper module DscResource.Common so they can be shared with the new resource SqlRSSetup. - Improved verbose messages in Test-TargetResource function to more clearly tell if features are already installed or not. - Refactored unit tests for the functions Test-TargetResource and Set-TargetResource to improve testing speed. - Modified the Test-TargetResource and Set-TargetResource to not be case-sensitive when comparing feature names. *This was handled correctly in real-world scenarios, but failed when running the unit tests (and testing casing).* - Changes to SqlDatabaseDefaultLocation - No longer does the Test-TargetResource fail on the second test run when the backup file path was changed, and the path was ending with a backslash (issue #1307). --- CHANGELOG.md | 33 +- DSCResources/MSFT_SqlAG/MSFT_SqlAG.psm1 | 11 +- .../MSFT_SqlAGDatabase.psm1 | 12 +- .../MSFT_SqlAGListener.psm1 | 12 +- .../MSFT_SqlAGReplica/MSFT_SqlAGReplica.psm1 | 11 +- .../MSFT_SqlAgentOperator.psm1 | 12 +- DSCResources/MSFT_SqlAlias/MSFT_SqlAlias.psm1 | 9 +- .../MSFT_SqlAlwaysOnService.psm1 | 11 +- .../MSFT_SqlDatabase/MSFT_SqlDatabase.psm1 | 12 +- .../MSFT_SqlDatabaseDefaultLocation.psm1 | 24 +- .../MSFT_SqlDatabaseOwner.psm1 | 14 +- .../MSFT_SqlDatabasePermission.psm1 | 14 +- .../MSFT_SqlDatabaseRecoveryModel.psm1 | 14 +- .../MSFT_SqlDatabaseRole.psm1 | 11 +- DSCResources/MSFT_SqlRS/MSFT_SqlRS.psm1 | 11 +- .../MSFT_SqlRSSetup/MSFT_SqlRSSetup.psm1 | 801 ++++ .../MSFT_SqlRSSetup.schema.mof | 21 + .../en-US/MSFT_SqlRSSetup.strings.psd1 | 25 + .../MSFT_SqlScript/MSFT_SqlScript.psm1 | 10 +- .../MSFT_SqlScriptQuery.psm1 | 10 +- .../MSFT_SqlServerConfiguration.psm1 | 12 +- .../MSFT_SqlServerDatabaseMail.psm1 | 14 +- .../MSFT_SqlServerEndpoint.psm1 | 12 +- .../MSFT_SqlServerEndpointPermission.psm1 | 12 +- .../MSFT_SqlServerEndpointState.psm1 | 12 +- .../MSFT_SqlServerLogin.psm1 | 21 +- .../MSFT_SqlServerMaxDop.psm1 | 12 +- .../MSFT_SqlServerMemory.psm1 | 9 +- .../MSFT_SqlServerNetwork.psm1 | 12 +- .../MSFT_SqlServerPermission.psm1 | 12 +- .../MSFT_SqlServerRole.psm1 | 11 +- .../MSFT_SqlServerSecureConnection.psm1 | 10 +- .../MSFT_SqlServiceAccount.psm1 | 11 +- DSCResources/MSFT_SqlSetup/MSFT_SqlSetup.psm1 | 261 +- .../en-US/MSFT_SqlSetup.strings.psd1 | 13 +- .../MSFT_SqlWaitForAG/MSFT_SqlWaitForAG.psm1 | 11 +- .../MSFT_SqlWindowsFirewall.psm1 | 10 +- Examples/README.md | 1 + .../SqlRSSetup/1-InstallReportingServices.ps1 | 31 + .../2-UninstallReportingServices.ps1 | 33 + .../DscResource.Common.psm1 | 818 +++- .../en-US/DscResource.Common.strings.psd1 | 27 +- .../sv-SE/DscResource.Common.strings.psd1 | 2 +- .../DscResource.LocalizationHelper.psm1 | 133 +- README.md | 102 + ...SFT_SqlAgentOperator.Integration.Tests.ps1 | 4 +- ...T_SqlAlwaysOnService.Integration.Tests.ps1 | 4 +- ...abaseDefaultLocation.Integration.Tests.ps1 | 13 +- ...MSFT_SqlDatabaseDefaultLocation.config.ps1 | 2 + .../MSFT_SqlRS.Integration.Tests.ps1 | 8 +- .../MSFT_SqlRSSetup.Integration.Tests.ps1 | 161 + Tests/Integration/MSFT_SqlRSSetup.config.ps1 | 90 + .../MSFT_SqlScript.Integration.Tests.ps1 | 4 +- .../MSFT_SqlScriptQuery.Integration.Tests.ps1 | 4 +- ...qlServerDatabaseMail.Integration.Tests.ps1 | 4 +- ...FT_SqlServerEndPoint.Integration.Tests.ps1 | 4 +- .../MSFT_SqlServerLogin.Integration.Tests.ps1 | 12 +- ...SFT_SqlServerNetwork.Integration.Tests.ps1 | 4 +- .../MSFT_SqlServerRole.Integration.Tests.ps1 | 14 +- ...rverSecureConnection.Integration.Tests.ps1 | 4 +- ...FT_SqlServiceAccount.Integration.Tests.ps1 | 16 +- .../MSFT_SqlSetup.Integration.Tests.ps1 | 6 +- Tests/Integration/README.md | 98 +- Tests/Unit/CommonResourceHelper.Tests.ps1 | 210 -- ...Tests.ps1 => DscResource.Common.Tests.ps1} | 3344 ++++++++++------- .../DscResource.LocalizationHelper.Tests.ps1 | 203 + Tests/Unit/MSFT_SqlAG.Tests.ps1 | 6 +- Tests/Unit/MSFT_SqlAGDatabase.Tests.ps1 | 10 +- Tests/Unit/MSFT_SqlAGListener.Tests.ps1 | 4 +- Tests/Unit/MSFT_SqlAGReplica.Tests.ps1 | 7 +- Tests/Unit/MSFT_SqlDatabase.Tests.ps1 | 1 - .../MSFT_SqlDatabaseDefaultLocation.Tests.ps1 | 103 +- .../Unit/MSFT_SqlDatabasePermission.Tests.ps1 | 2 +- Tests/Unit/MSFT_SqlDatabaseRole.Tests.ps1 | 2 +- Tests/Unit/MSFT_SqlRS.Tests.ps1 | 3 +- Tests/Unit/MSFT_SqlRSSetup.Tests.ps1 | 958 +++++ Tests/Unit/MSFT_SqlScript.Tests.ps1 | 249 +- Tests/Unit/MSFT_SqlScriptQuery.Tests.ps1 | 253 +- Tests/Unit/MSFT_SqlServerEndpoint.Tests.ps1 | 2 +- ...MSFT_SqlServerEndpointPermission.Tests.ps1 | 2 +- .../MSFT_SqlServerEndpointState.Tests.ps1 | 4 +- Tests/Unit/MSFT_SqlServerLogin.Tests.ps1 | 7 +- Tests/Unit/MSFT_SqlServerMaxDop.Tests.ps1 | 5 +- Tests/Unit/MSFT_SqlServerMemory.Tests.ps1 | 5 +- Tests/Unit/MSFT_SqlServerPermission.Tests.ps1 | 2 +- .../Unit/MSFT_SqlServerReplication.Tests.ps1 | 3 +- .../MSFT_SqlServerSecureConnection.Tests.ps1 | 3 +- Tests/Unit/MSFT_SqlServiceAccount.Tests.ps1 | 4 +- Tests/Unit/MSFT_SqlSetup.Tests.ps1 | 1607 ++------ 89 files changed, 6192 insertions(+), 3939 deletions(-) create mode 100644 DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.psm1 create mode 100644 DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.schema.mof create mode 100644 DSCResources/MSFT_SqlRSSetup/en-US/MSFT_SqlRSSetup.strings.psd1 create mode 100644 Examples/Resources/SqlRSSetup/1-InstallReportingServices.ps1 create mode 100644 Examples/Resources/SqlRSSetup/2-UninstallReportingServices.ps1 rename SqlServerDscHelper.psm1 => Modules/DscResource.Common/DscResource.Common.psm1 (79%) rename en-US/SqlServerDscHelper.strings.psd1 => Modules/DscResource.Common/en-US/DscResource.Common.strings.psd1 (89%) rename sv-SE/SqlServerDscHelper.strings.psd1 => Modules/DscResource.Common/sv-SE/DscResource.Common.strings.psd1 (99%) rename DSCResources/CommonResourceHelper.psm1 => Modules/DscResource.LocalizationHelper/DscResource.LocalizationHelper.psm1 (89%) create mode 100644 Tests/Integration/MSFT_SqlRSSetup.Integration.Tests.ps1 create mode 100644 Tests/Integration/MSFT_SqlRSSetup.config.ps1 delete mode 100644 Tests/Unit/CommonResourceHelper.Tests.ps1 rename Tests/Unit/{SqlServerDSCHelper.Tests.ps1 => DscResource.Common.Tests.ps1} (64%) create mode 100644 Tests/Unit/DscResource.LocalizationHelper.Tests.ps1 create mode 100644 Tests/Unit/MSFT_SqlRSSetup.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 38e706e14..827851b12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ ## Unreleased +- Changes to SqlServerDsc + - Added new resources. + - SqlRSSetup + - Added helper module DscResource.Common from the repository + DscResource.Template. + - Moved all helper functions from SqlServerDscHelper.psm1 to DscResource.Common. + - Renamed Test-SqlDscParameterState to Test-DscParameterState. + - New-TerminatingError error text for a missing localized message now matches + the output even if the "missing localized message" localized message is + also missing. + - Added helper module DscResource.LocalizationHelper from the repository + DscResource.Template, this replaces the helper module CommonResourceHelper.psm1. + - Cleaned up unit tests, mostly around loading cmdlet stubs and loading + classes stubs, but also some tests that were using some odd variants. + - Fix all integration tests according to issue [PowerShell/DscResource.Template#14](https://github.com/PowerShell/DscResource.Template/issues/14). - Changes to SqlServerMemory - Updated Cim Class to Win32_ComputerSystem (instead of Win32_PhysicalMemory) because the correct memory size was not being detected correctly on Azure VMs @@ -33,14 +48,20 @@ - Changed the logic of 'Build the argument string to be passed to setup' to not quote the value if root directory is specified ([issue #1254](https://github.com/PowerShell/SqlServerDsc/issues/1254)). + - Moved some resource specific helper functions to the new helper module + DscResource.Common so they can be shared with the new resource SqlRSSetup. + - Improved verbose messages in Test-TargetResource function to more + clearly tell if features are already installed or not. + - Refactored unit tests for the functions Test-TargetResource and + Set-TargetResource to improve testing speed. + - Modified the Test-TargetResource and Set-TargetResource to not be + case-sensitive when comparing feature names. *This was handled + correctly in real-world scenarios, but failed when running the unit + tests (and testing casing).* - Changes to SqlAGDatabase - Fix MatchDatabaseOwner to check for CONTROL SERVER, IMPERSONATE LOGIN, or CONTROL LOGIN permission in addition to IMPERSONATE ANY LOGIN. - Update and fix MatchDatabaseOwner help text. -- Changes to xSQLServerHelper - - New-TerminatingError error text for a missing localized message now matches - the output even if the "missing localized message" localized message is - also missing. - Changes to SqlAG - Updated documentation on the behaviour of defaults as they only apply when creating a group. @@ -52,6 +73,10 @@ run ([issue #518](https://github.com/PowerShell/SqlServerDsc/issues/518)). - Test-Resource fixed to report whether ReadOnlyRoutingList desired state has been reached correctly ([issue #1305](https://github.com/PowerShell/SqlServerDsc/issues/1305)). +- Changes to SqlDatabaseDefaultLocation + - No longer does the Test-TargetResource fail on the second test run + when the backup file path was changed, and the path was ending with + a backslash ([issue #1307](https://github.com/PowerShell/SqlServerDsc/issues/1307)). ## 12.3.0.0 diff --git a/DSCResources/MSFT_SqlAG/MSFT_SqlAG.psm1 b/DSCResources/MSFT_SqlAG/MSFT_SqlAG.psm1 index 54120a639..fb3ec33e0 100644 --- a/DSCResources/MSFT_SqlAG/MSFT_SqlAG.psm1 +++ b/DSCResources/MSFT_SqlAG/MSFT_SqlAG.psm1 @@ -1,6 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlAGDatabase/MSFT_SqlAGDatabase.psm1 b/DSCResources/MSFT_SqlAGDatabase/MSFT_SqlAGDatabase.psm1 index 58381970c..507532df4 100644 --- a/DSCResources/MSFT_SqlAGDatabase/MSFT_SqlAGDatabase.psm1 +++ b/DSCResources/MSFT_SqlAGDatabase/MSFT_SqlAGDatabase.psm1 @@ -1,9 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlAGDatabase' diff --git a/DSCResources/MSFT_SqlAGListener/MSFT_SqlAGListener.psm1 b/DSCResources/MSFT_SqlAGListener/MSFT_SqlAGListener.psm1 index 69a01d975..2ca4e66df 100644 --- a/DSCResources/MSFT_SqlAGListener/MSFT_SqlAGListener.psm1 +++ b/DSCResources/MSFT_SqlAGListener/MSFT_SqlAGListener.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS Returns the current state of the Availability Group listener. diff --git a/DSCResources/MSFT_SqlAGReplica/MSFT_SqlAGReplica.psm1 b/DSCResources/MSFT_SqlAGReplica/MSFT_SqlAGReplica.psm1 index 6c129416c..12808d29d 100644 --- a/DSCResources/MSFT_SqlAGReplica/MSFT_SqlAGReplica.psm1 +++ b/DSCResources/MSFT_SqlAGReplica/MSFT_SqlAGReplica.psm1 @@ -1,6 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlAgentOperator/MSFT_SqlAgentOperator.psm1 b/DSCResources/MSFT_SqlAgentOperator/MSFT_SqlAgentOperator.psm1 index f1c0d0047..aa292485b 100644 --- a/DSCResources/MSFT_SqlAgentOperator/MSFT_SqlAgentOperator.psm1 +++ b/DSCResources/MSFT_SqlAgentOperator/MSFT_SqlAgentOperator.psm1 @@ -1,9 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlAgentOperator' diff --git a/DSCResources/MSFT_SqlAlias/MSFT_SqlAlias.psm1 b/DSCResources/MSFT_SqlAlias/MSFT_SqlAlias.psm1 index 02fc47d98..de21afa39 100644 --- a/DSCResources/MSFT_SqlAlias/MSFT_SqlAlias.psm1 +++ b/DSCResources/MSFT_SqlAlias/MSFT_SqlAlias.psm1 @@ -1,4 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'SqlServerDscHelper.psm1') -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') function Get-TargetResource { diff --git a/DSCResources/MSFT_SqlAlwaysOnService/MSFT_SqlAlwaysOnService.psm1 b/DSCResources/MSFT_SqlAlwaysOnService/MSFT_SqlAlwaysOnService.psm1 index 4f6aa384e..5c7683c38 100644 --- a/DSCResources/MSFT_SqlAlwaysOnService/MSFT_SqlAlwaysOnService.psm1 +++ b/DSCResources/MSFT_SqlAlwaysOnService/MSFT_SqlAlwaysOnService.psm1 @@ -1,6 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlDatabase/MSFT_SqlDatabase.psm1 b/DSCResources/MSFT_SqlDatabase/MSFT_SqlDatabase.psm1 index 0f3bd5150..afa5a205b 100644 --- a/DSCResources/MSFT_SqlDatabase/MSFT_SqlDatabase.psm1 +++ b/DSCResources/MSFT_SqlDatabase/MSFT_SqlDatabase.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS This function gets the sql database. diff --git a/DSCResources/MSFT_SqlDatabaseDefaultLocation/MSFT_SqlDatabaseDefaultLocation.psm1 b/DSCResources/MSFT_SqlDatabaseDefaultLocation/MSFT_SqlDatabaseDefaultLocation.psm1 index cd02b9b39..d6f908048 100644 --- a/DSCResources/MSFT_SqlDatabaseDefaultLocation/MSFT_SqlDatabaseDefaultLocation.psm1 +++ b/DSCResources/MSFT_SqlDatabaseDefaultLocation/MSFT_SqlDatabaseDefaultLocation.psm1 @@ -1,9 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlDatabaseDefaultLocation' @@ -145,6 +147,12 @@ Function Set-TargetResource $ProcessOnlyOnActiveNode ) + if ($Type -eq 'Backup') + { + # Ending backslash is removed because of issue #1307. + $Path = $Path.TrimEnd('\') + } + # Make sure the Path exists, needs to be cluster aware as well for this check if (-Not (Test-Path $Path)) { @@ -263,6 +271,12 @@ function Test-TargetResource Write-Verbose -Message ($script:localizedData.TestingCurrentPath -f $Type) + if ($Type -eq 'Backup') + { + # Ending backslash is removed because of issue #1307. + $Path = $Path.TrimEnd('\') + } + $getTargetResourceParameters = @{ InstanceName = $InstanceName ServerName = $ServerName diff --git a/DSCResources/MSFT_SqlDatabaseOwner/MSFT_SqlDatabaseOwner.psm1 b/DSCResources/MSFT_SqlDatabaseOwner/MSFT_SqlDatabaseOwner.psm1 index ce7268158..c1005ebce 100644 --- a/DSCResources/MSFT_SqlDatabaseOwner/MSFT_SqlDatabaseOwner.psm1 +++ b/DSCResources/MSFT_SqlDatabaseOwner/MSFT_SqlDatabaseOwner.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS This function gets the owner of the desired sql database. @@ -200,7 +206,7 @@ function Test-TargetResource Write-Verbose -Message "Testing owner $Name of database $Database" $currentValues = Get-TargetResource @PSBoundParameters - return Test-SQLDscParameterState -CurrentValues $CurrentValues ` + return Test-DscParameterState -CurrentValues $CurrentValues ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @('Name', 'Database') } diff --git a/DSCResources/MSFT_SqlDatabasePermission/MSFT_SqlDatabasePermission.psm1 b/DSCResources/MSFT_SqlDatabasePermission/MSFT_SqlDatabasePermission.psm1 index 178180639..6904f0bb4 100644 --- a/DSCResources/MSFT_SqlDatabasePermission/MSFT_SqlDatabasePermission.psm1 +++ b/DSCResources/MSFT_SqlDatabasePermission/MSFT_SqlDatabasePermission.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS Returns the current permissions for the user in the database @@ -402,7 +408,7 @@ function Test-TargetResource the value 'Present' for the Ensure parameter, otherwise Ensure will have the value 'Absent'. #> - return Test-SQLDscParameterState -CurrentValues $getTargetResourceResult ` + return Test-DscParameterState -CurrentValues $getTargetResourceResult ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @('Name', 'Ensure', 'PermissionState') } diff --git a/DSCResources/MSFT_SqlDatabaseRecoveryModel/MSFT_SqlDatabaseRecoveryModel.psm1 b/DSCResources/MSFT_SqlDatabaseRecoveryModel/MSFT_SqlDatabaseRecoveryModel.psm1 index b5d698536..4d2c52792 100644 --- a/DSCResources/MSFT_SqlDatabaseRecoveryModel/MSFT_SqlDatabaseRecoveryModel.psm1 +++ b/DSCResources/MSFT_SqlDatabaseRecoveryModel/MSFT_SqlDatabaseRecoveryModel.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS This function gets all Key properties defined in the resource schema file @@ -191,7 +197,7 @@ function Test-TargetResource $currentValues = Get-TargetResource @PSBoundParameters - return Test-SQLDscParameterState -CurrentValues $currentValues ` + return Test-DscParameterState -CurrentValues $currentValues ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @('Name', 'RecoveryModel') } diff --git a/DSCResources/MSFT_SqlDatabaseRole/MSFT_SqlDatabaseRole.psm1 b/DSCResources/MSFT_SqlDatabaseRole/MSFT_SqlDatabaseRole.psm1 index df2e34ffd..8ddbb884d 100644 --- a/DSCResources/MSFT_SqlDatabaseRole/MSFT_SqlDatabaseRole.psm1 +++ b/DSCResources/MSFT_SqlDatabaseRole/MSFT_SqlDatabaseRole.psm1 @@ -1,6 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlRS/MSFT_SqlRS.psm1 b/DSCResources/MSFT_SqlRS/MSFT_SqlRS.psm1 index 227dfa040..2f96ed6d2 100644 --- a/DSCResources/MSFT_SqlRS/MSFT_SqlRS.psm1 +++ b/DSCResources/MSFT_SqlRS/MSFT_SqlRS.psm1 @@ -1,6 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.psm1 b/DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.psm1 new file mode 100644 index 000000000..5db49aeeb --- /dev/null +++ b/DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.psm1 @@ -0,0 +1,801 @@ +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlRSSetup' + +<# + .SYNOPSIS + Returns the current state of the Microsoft SQL Server Reporting Service + instance. + + .PARAMETER InstanceName + Name of the Microsoft SQL Server Reporting Service instance to installed. + This can only be set to 'SSRS'. { 'SSRS' } + + .PARAMETER IAcceptLicenseTerms + Accept licens terms. This must be set to 'Yes'. { 'Yes' } + + .PARAMETER SourcePath + The path to the installation media file to be used for installation, + e.g an UNC path to a shared resource. Environment variables can be used + in the path. + + .NOTES + The following properties are always returning $null because it's currently + unknown how to return that information. + - ProductKey + - Edition + + The following properties always return $null on purpose. This could be + changed in the future. + - Action + - SourceCredential + - ForceRestart + - EditionUpgrade + - VersionUpgrade + - LogPath + +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('SSRS')] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Yes')] + [System.String] + $IAcceptLicenseTerms, + + [Parameter(Mandatory = $true)] + [System.String] + $SourcePath + ) + + $returnObject = @{ + InstanceName = $null + IAcceptLicenseTerms = $IAcceptLicenseTerms + SourcePath = $SourcePath + Action = $null + SourceCredential = $null + ProductKey = $null + ForceRestart = $false + EditionUpgrade = $false + VersionUpgrade = $false + Edition = $null + LogPath = $null + InstallFolder = $null + ErrorDumpDirectory = $null + CurrentVersion = $null + ServiceName = $null + } + + $InstanceName = $InstanceName.ToUpper() + + $getRegistryPropertyValueParameters = @{ + Path = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS' + Name = $InstanceName + } + + $reportingServiceInstanceId = Get-RegistryPropertyValue @getRegistryPropertyValueParameters + if ($reportingServiceInstanceId) + { + Write-Verbose -Message ( + $script:localizedData.FoundInstance -f $InstanceName + ) + + # InstanceName + $returnObject['InstanceName'] = $InstanceName + + # InstallFolder + $getRegistryPropertyValueParameters = @{ + Path = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\SSRS\Setup' + Name = 'InstallRootDirectory' + } + + $returnObject['InstallFolder'] = Get-RegistryPropertyValue @getRegistryPropertyValueParameters + + # ServiceName + $getRegistryPropertyValueParameters['Name'] = 'ServiceName' + + $returnObject['ServiceName'] = Get-RegistryPropertyValue @getRegistryPropertyValueParameters + + # ErrorDumpDirectory + $getRegistryPropertyValueParameters = @{ + Path = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\SSRS\CPE' + Name = 'ErrorDumpDir' + } + + $returnObject['ErrorDumpDirectory'] = Get-RegistryPropertyValue @getRegistryPropertyValueParameters + + # CurrentVersion + $getPackageParameters = @{ + Name = 'Microsoft SQL Server Reporting Services' + ProviderName = 'Programs' + ErrorAction = 'SilentlyContinue' + # Get-Package returns a lot of excessive information that we don't need. + Verbose = $false + } + + $reportingServicesPackage = Get-Package @getPackageParameters + if ($reportingServicesPackage) + { + Write-Verbose -Message ( + $script:localizedData.VersionFound -f $reportingServicesPackage.Version + ) + + $returnObject['CurrentVersion'] = $reportingServicesPackage.Version + } + else + { + Write-Warning -Message $script:localizedData.PackageNotFound + } + } + else + { + Write-Verbose -Message ( + $script:localizedData.InstanceNotFound -f $InstanceName + ) + } + + return $returnObject +} + +<# + .SYNOPSIS + Installs the the Microsoft SQL Server Reporting Service instance. + + .PARAMETER InstanceName + Name of the Microsoft SQL Server Reporting Service instance to installed. + This can only be set to 'SSRS'. { 'SSRS' } + + .PARAMETER IAcceptLicenseTerms + Accept licens terms. This must be set to 'Yes'. { 'Yes' } + + .PARAMETER SourcePath + The path to the installation media file to be used for installation, + e.g an UNC path to a shared resource. Environment variables can be used + in the path. + + .PARAMETER Action + The action to be performed. Default value is 'Install' which performs + either install or upgrade. + { *Install* | Uninstall } + + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter 'SourcePath'. + + .PARAMETER SuppressRestart + Suppresses any attempts to restart. + + .PARAMETER ProductKey + Sets the custom license key, e.g. '12345-12345-12345-12345-12345'. + + .PARAMETER ForceRestart + Forces a restart after installation is finished. + + .PARAMETER EditionUpgrade + Upgrades the edition of the installed product. Requires that either the + ProductKey or the Edition parameter is also assigned. Default is $false. + + .PARAMETER VersionUpgrade + Upgrades installed product version, if the major product version of the + source executable is higher than the major current version. Requires that + either the ProductKey or the Edition parameter is also assigned. Default + is $false. + + Not used in Set-TargetResource. The default is that the installation + does upgrade. This variable is only used in Test-TargetResource to return + false if the major version is different. + + .PARAMETER Edition + Sets the custom free edition. + { 'Development' | 'Evaluation' | 'ExpressAdvanced' } + + .PARAMETER LogPath + Specifies the setup log file location, e.g. 'log.txt'. By default, log + files are created under %TEMP%. + + .PARAMETER InstallFolder + Sets the install folder, e.g. 'C:\Program Files\SSRS'. Default value is + 'C:\Program Files\Microsoft SQL Server Reporting Services'. + + .PARAMETER SetupProcessTimeout + The timeout, in seconds, to wait for the setup process to finish. + Default value is 7200 seconds (2 hours). If the setup process does not + finish before this time, and error will be thrown. +#> +function Set-TargetResource +{ + <# + Suppressing this rule because $global:DSCMachineStatus is used to trigger + a Restart, either by force or when there are pending changes. + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] + <# + Suppressing this rule because $global:DSCMachineStatus is only set, + never used (by design of Desired State Configuration). + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function', Target='DSCMachineStatus')] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('SSRS')] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Yes')] + [System.String] + $IAcceptLicenseTerms, + + [Parameter(Mandatory = $true)] + [System.String] + $SourcePath, + + [Parameter()] + [ValidateSet('Install','Uninstall')] + [System.String] + $Action = 'Install', + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential, + + [Parameter()] + [System.Boolean] + $SuppressRestart, + + [Parameter()] + [System.String] + $ProductKey, + + [Parameter()] + [System.Boolean] + $ForceRestart, + + [Parameter()] + [System.Boolean] + $EditionUpgrade, + + [Parameter()] + [System.Boolean] + $VersionUpgrade, + + [Parameter()] + [ValidateSet('Development','Evaluation','ExpressAdvanced')] + [System.String] + $Edition, + + [Parameter()] + [System.String] + $LogPath, + + [Parameter()] + [System.String] + $InstallFolder, + + [Parameter()] + [System.UInt32] + $SetupProcessTimeout = 7200 + ) + + # Must either choose ProductKey or Edition, not both. + if ($Action -eq 'Install' -and $PSBoundParameters.ContainsKey('Edition') -and $PSBoundParameters.ContainsKey('ProductKey')) + { + $errorMessage = $script:localizedData.EditionInvalidParameter + New-InvalidArgumentException -ArgumentName 'Edition, ProductKey' -Message $errorMessage + } + + # Must either choose ProductKey or Edition, not none. + if ($Action -eq 'Install' -and -not $PSBoundParameters.ContainsKey('Edition') -and -not $PSBoundParameters.ContainsKey('ProductKey')) + { + $errorMessage = $script:localizedData.EditionMissingParameter + New-InvalidArgumentException -ArgumentName 'Edition, ProductKey' -Message $errorMessage + } + + if (-not (Test-Path -Path $SourcePath) -or (Get-Item -Path $SourcePath).Extension -ne '.exe') + { + $errorMessage = $script:localizedData.SourcePathNotFound -f $SourcePath + New-InvalidArgumentException -ArgumentName 'SourcePath' -Message $errorMessage + } + + $InstanceName = $InstanceName.ToUpper() + + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) + + $parametersToEvaluateTrailingSlash = @( + 'SourcePath', + 'InstallFolder' + ) + + # Making sure paths are correct. + foreach ($parameterName in $parametersToEvaluateTrailingSlash) + { + if ($PSBoundParameters.ContainsKey($parameterName)) + { + $parameterValue = Get-Variable -Name $parameterName -ValueOnly + $formattedPath = Format-Path -Path $parameterValue -TrailingSlash + Set-Variable -Name $parameterName -Value $formattedPath + } + } + + if ($SourceCredential) + { + $executableParentFolder = Split-Path -Path $SourcePath -Parent + $executableFileName = Split-Path -Path $SourcePath -Leaf + + $invokeInstallationMediaCopyParameters = @{ + SourcePath = $executableParentFolder + SourceCredential = $SourceCredential + PassThru = $true + } + + $newExecutableParentFolder = Invoke-InstallationMediaCopy @invokeInstallationMediaCopyParameters + + # Switch SourcePath to point to the new local location. + $SourcePath = Join-Path -Path $newExecutableParentFolder -ChildPath $executableFileName + } + + Write-Verbose -Message ($script:localizedData.UsingExecutable -f $SourcePath) + + $setupArguments = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + } + + if ($Action -eq 'Install') + { + $setupArguments += @{ + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + } + } + else + { + $setupArguments += @{ + 'uninstall' = [System.Management.Automation.SwitchParameter] $true + } + } + + <# + This is a list of parameters that are allowed to be translated into + arguments. + #> + $allowedParametersAsArguments = @( + 'ProductKey' + 'SuppressRestart' + 'EditionUpgrade' + 'Edition' + 'LogPath' + 'InstallFolder' + ) + + $argumentParameters = $PSBoundParameters.Keys | Where-Object -FilterScript { + $_ -in $allowedParametersAsArguments + } + + <# + Handle translation between parameter name and argument name. + Also making sure using the correct casing, e.g. 'log' and not 'Log'. + #> + switch ($argumentParameters) + { + 'ProductKey' + { + $setupArguments += @{ + 'PID' = $ProductKey + } + } + + 'SuppressRestart' + { + $setupArguments += @{ + 'norestart' = [System.Management.Automation.SwitchParameter] $true + } + } + + 'EditionUpgrade' + { + $setupArguments += @{ + 'EditionUpgrade' = [System.Management.Automation.SwitchParameter] $true + } + } + + 'Edition' + { + $setupArguments += @{ + 'Edition' = Convert-EditionName -Name $Edition + } + } + + 'LogPath' + { + $setupArguments += @{ + 'log' = $LogPath + } + } + + default + { + $setupArguments += @{ + $_ = Get-Variable -Name $_ -ValueOnly + } + } + } + + # Build the argument string to be passed to setup + $argumentString = '' + foreach ($currentSetupArgument in $setupArguments.GetEnumerator()) + { + # Arrays are handled specially + if ($currentSetupArgument.Value -is [System.Management.Automation.SwitchParameter]) + { + $argumentString += '/{0}' -f $currentSetupArgument.Key + } + else + { + $argumentString += '/{0}={1}' -f $currentSetupArgument.Key, $currentSetupArgument.Value + } + + # Add a space between arguments. + $argumentString += ' ' + } + + # Trim whitespace at start and end of string. + $argumentString = $argumentString.Trim() + + # Save the arguments for the log output + $logOutput = $argumentString + + # Replace sensitive values for verbose output + if ($PSBoundParameters.ContainsKey('ProductKey')) + { + $logOutput = $logOutput -replace $ProductKey, '*****-*****-*****-*****-*****' + } + + Write-Verbose -Message ($script:localizedData.SetupArguments -f $logOutput) + + <# + This handles when PsDscRunAsCredential is set, or running + as the SYSTEM account. + #> + + $startProcessParameters = @{ + FilePath = $SourcePath + ArgumentList = $argumentString + Timeout = $SetupProcessTimeout + } + + $processExitCode = Start-SqlSetupProcess @startProcessParameters + + Write-Verbose -Message ($script:localizedData.SetupExitMessage -f $processExitCode) + + if ($processExitCode -eq 0) + { + Write-Verbose -Message ($script:localizedData.SetupSuccessful -f $script:localizedData.$Action) + } + elseif ($processExitCode -eq 3010) + { + Write-Warning -Message ($script:localizedData.SetupSuccessfulRestartRequired -f $script:localizedData.$Action) + + $global:DSCMachineStatus = 1 + } + else + { + if ($PSBoundParameters.ContainsKey('LogPath')) + { + $errorMessage = $script:localizedData.SetupFailedWithLog -f $LogPath + } + else + { + $errorMessage = $script:localizedData.SetupFailed + } + + New-InvalidResultException -Message $errorMessage + } + + <# + If ForceRestart is set it will always restart, and override SuppressRestart. + If SuppressRestart is set it will always override any pending restart. + #> + if ($ForceRestart) + { + $global:DSCMachineStatus = 1 + } + elseif ($global:DSCMachineStatus -eq 1 -and $SuppressRestart) + { + # Suppressing restart to make sure the node is not restarted. + $global:DSCMachineStatus = 0 + + Write-Verbose -Message $script:localizedData.SuppressRestart + } + elseif (-not $SuppressRestart -and (Test-PendingRestart)) + { + $global:DSCMachineStatus = 1 + } + + if ($global:DSCMachineStatus -eq 1) + { + Write-Verbose -Message $script:localizedData.Restart + } +} + +<# + .SYNOPSIS + Tests if the Microsoft SQL Server Reporting Service instance is installed. + + .PARAMETER InstanceName + Name of the Microsoft SQL Server Reporting Service instance to installed. + This can only be set to 'SSRS'. { 'SSRS' } + + .PARAMETER IAcceptLicenseTerms + Accept licens terms. This must be set to 'Yes'. { 'Yes' } + + .PARAMETER SourcePath + The path to the installation media file to be used for installation, + e.g an UNC path to a shared resource. Environment variables can be used + in the path. + + .PARAMETER Action + The action to be performed. Default value is 'Install' which performs + either install or upgrade. + { *Install* | Uninstall } + + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter 'SourcePath'. + + .PARAMETER SuppressRestart + Suppresses any attempts to restart. + + .PARAMETER ProductKey + Sets the custom license key, e.g. '12345-12345-12345-12345-12345'. + + .PARAMETER ForceRestart + Forces a restart after installation is finished. + + .PARAMETER EditionUpgrade + Upgrades the edition of the installed product. Requires that either the + ProductKey or the Edition parameter is also assigned. Default is $false. + + .PARAMETER VersionUpgrade + Upgrades installed product version, if the major product version of the + source executable is higher than the major current version. Requires that + either the ProductKey or the Edition parameter is also assigned. Default + is $false. + + .PARAMETER Edition + Sets the custom free edition. + { 'Development' | 'Evaluation' | 'ExpressAdvanced' } + + .PARAMETER LogPath + Specifies the setup log file location, e.g. 'log.txt'. By default, log + files are created under %TEMP%. + + .PARAMETER InstallFolder + Sets the install folder, e.g. 'C:\Program Files\SSRS'. Default value is + 'C:\Program Files\Microsoft SQL Server Reporting Services'. + + .PARAMETER SetupProcessTimeout + The timeout, in seconds, to wait for the setup process to finish. + Default value is 7200 seconds (2 hours). If the setup process does not + finish before this time, and error will be thrown. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('SSRS')] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Yes')] + [System.String] + $IAcceptLicenseTerms, + + [Parameter(Mandatory = $true)] + [System.String] + $SourcePath, + + [Parameter()] + [ValidateSet('Install','Uninstall')] + [System.String] + $Action = 'Install', + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential, + + [Parameter()] + [System.Boolean] + $SuppressRestart, + + [Parameter()] + [System.String] + $ProductKey, + + [Parameter()] + [System.Boolean] + $ForceRestart, + + [Parameter()] + [System.Boolean] + $EditionUpgrade, + + [Parameter()] + [System.Boolean] + $VersionUpgrade, + + [Parameter()] + [ValidateSet('Development','Evaluation','ExpressAdvanced')] + [System.String] + $Edition, + + [Parameter()] + [System.String] + $LogPath, + + [Parameter()] + [System.String] + $InstallFolder, + + [Parameter()] + [System.UInt32] + $SetupProcessTimeout = 7200 + ) + + Write-Verbose -Message ( + $script:localizedData.TestingConfiguration + ) + + $getTargetResourceParameters = @{ + InstanceName = $InstanceName + IAcceptLicenseTerms = $IAcceptLicenseTerms + SourcePath = $SourcePath + } + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $returnValue = $false + + <# + We determine if the Microsoft SQL Server Reporting Service instance is + installed if the instance name is found in the registry. + #> + if ($Action -eq 'Install') + { + $fileVersion = Get-FileProductVersion -Path $SourcePath + + if ($getTargetResourceResult.InstanceName) + { + $installedVersion = [System.Version] $getTargetResourceResult.CurrentVersion + + # The major version is evaluated if VersionUpgrade is set to $true + if (-not $VersionUpgrade -or ($VersionUpgrade -and $installedVersion -ge $fileVersion)) + { + $returnValue = $true + } + else + { + Write-Verbose -Message ( + $script:localizedData.WrongVersionFound ` + -f $fileVersion.ToString(), $installedVersion.ToString() + ) + } + } + else + { + Write-Verbose -Message ( + $script:localizedData.MissingVersion ` + -f $fileVersion.ToString() + ) + } + } + + if ($Action -eq 'Uninstall' -and $null -eq $getTargetResourceResult.InstanceName) + { + $returnValue = $true + } + + return $returnValue +} + +<# + .SYNOPSIS + Converts between the edition names used by the resource and the + installation media. + + .PARAMETER Name + The edition name to convert. + + .OUTPUTS + Returns the equivalent name of what was provided in the parameter Name. + For example, if Name is set to 'Dev', the cmdlet returns 'Development'. + If Name is set to 'Development', the cmdlet returns 'Dev'. +#> +function Convert-EditionName +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + switch ($Name) + { + # Resource edition names + 'Development' + { + $convertEditionNameResult = 'Dev' + } + + 'Evaluation' + { + $convertEditionNameResult = 'Eval' + } + + 'ExpressAdvanced' + { + $convertEditionNameResult = 'ExprAdv' + } + + # Installation media edition names + 'Dev' + { + $convertEditionNameResult = 'Development' + } + + 'Eval' + { + $convertEditionNameResult = 'Evaluation' + } + + 'ExprAdv' + { + $convertEditionNameResult = 'ExpressAdvanced' + } + } + + return $convertEditionNameResult +} + +<# + .SYNOPSIS + Gets the product version of a executable. + + .PARAMETER Path + The path to the executable to return product version for. + + .OUTPUTS + Returns the product version as [System.Version] type. +#> +function Get-FileProductVersion +{ + [CmdletBinding()] + [OutputType([System.Version])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Path + ) + + return [System.Version] (Get-Item -Path $Path).VersionInfo.ProductVersion +} diff --git a/DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.schema.mof b/DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.schema.mof new file mode 100644 index 000000000..95378034d --- /dev/null +++ b/DSCResources/MSFT_SqlRSSetup/MSFT_SqlRSSetup.schema.mof @@ -0,0 +1,21 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SqlRSSetup")] +class MSFT_SqlRSSetup : OMI_BaseResource +{ + [Key, Description("Name of the Microsoft SQL Server Reporting Service instance to installed. This can only be set to 'SSRS'. { 'SSRS' }"), ValueMap{"SSRS"}, Values{"SSRS"}] String InstanceName; + [Required, Description("Accept licens terms. This must be set to 'Yes'. { 'Yes' }"), ValueMap{"Yes"}, Values{"Yes"}] String IAcceptLicenseTerms; + [Required, Description("The path to the installation media file to be used for installation, e.g an UNC path to a shared resource. Environment variables can be used in the path.")] String SourcePath; + [Write, Description("The action to be performed. Default value is 'Install' which performs either install or upgrade. { *Install* | Uninstall }"), ValueMap{"Install","Uninstall"}, Values{"Install","Uninstall"}] String Action; + [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath'.")] String SourceCredential; + [Write, Description("Suppresses any attempts to restart.")] Boolean SuppressRestart; + [Write, Description("Sets the custom license key, e.g. '12345-12345-12345-12345-12345'.")] String ProductKey; + [Write, Description("Forces a restart after installation is finished.")] Boolean ForceRestart; + [Write, Description("Upgrades the edition of the installed product. Requires that either the ProductKey or the Edition parameter is also assigned. Default is $false.")] Boolean EditionUpgrade; + [Write, Description("Upgrades installed product version, if the major product version of the source executable is higher than the major current version. Requires that either the ProductKey or the Edition parameter is also assigned. Default is $false.")] Boolean VersionUpgrade; + [Write, Description("Sets the custom free edition. { 'Development' | 'Evaluation' | 'ExpressAdvanced' }"), ValueMap{"Development","Evaluation","ExpressAdvanced"}, Values{"Development","Evaluation","ExpressAdvanced"}] String Edition; + [Write, Description("Specifies the setup log file location, e.g. 'log.txt'. By default, log files are created under %TEMP%.")] String LogPath; + [Write, Description("Sets the install folder, e.g. 'C:\\Program Files\\SSRS'. Default value is 'C:\\Program Files\\Microsoft SQL Server Reporting Services'.")] String InstallFolder; + [Write, Description("The timeout, in seconds, to wait for the setup process to finish. Default value is 7200 seconds (2 hours). If the setup process does not finish before this time, and error will be thrown.")] UInt32 SetupProcessTimeout; + [Read, Description("Returns the path to error dump log files.")] String ErrorDumpDirectory; + [Read, Description("Returns the current version of the installed Microsoft SQL Server Reporting Service instance.")] String CurrentVersion; + [Read, Description("Returns the current name of the Microsoft SQL Server Reporting Service instance Windows service.")] String ServiceName; +}; diff --git a/DSCResources/MSFT_SqlRSSetup/en-US/MSFT_SqlRSSetup.strings.psd1 b/DSCResources/MSFT_SqlRSSetup/en-US/MSFT_SqlRSSetup.strings.psd1 new file mode 100644 index 000000000..f29d8ea1b --- /dev/null +++ b/DSCResources/MSFT_SqlRSSetup/en-US/MSFT_SqlRSSetup.strings.psd1 @@ -0,0 +1,25 @@ +# Localized resources for SqlSetup + +ConvertFrom-StringData @' + TestingConfiguration = Determines if the Microsoft SQL Server Reporting Service instance is installed. + FoundInstance = Found Microsoft SQL Server Reporting Service instance named '{0}'. + InstanceNotFound = Could not find a Microsoft SQL Server Reporting Service instance. + Install = Install + Uninstall = Uninstall + UsingExecutable = Using executable at '{0}'. + SetupArguments = Starting executable using the arguments: {0} + SetupExitMessage = Executable exited with code '{0}'. + SetupSuccessfulRestartRequired = {0} finished successfully, but a restart is required. + SetupFailed = Please see the log file in the %TEMP% folder. + SetupFailedWithLog = Please see the log file '{0}'. If not path was provided, the default path for log files is %TEMP%. + SetupSuccessful = {0} finished successfully. + Restart = Restarting the target node. + SuppressRestart = Suppressing restart of target node. + EditionInvalidParameter = Both the parameters Edition and ProductKey was specified. Only either parameter Edition or ProductKey is allowed. + EditionMissingParameter = Neither the parameters Edition and ProductKey was specified. + SourcePathNotFound = The source path '{0}' does not exist, or the path does not specify an executable file. + VersionFound = The Microsoft SQL Server Reporting Service instance is version '{0}'. + PackageNotFound = Could not determine the version of the Microsoft SQL Server Reporting Service instance. + WrongVersionFound = Expected version '{0}', but version '{1}' is installed. + MissingVersion = Expected version '{0}' to be installed. +'@ diff --git a/DSCResources/MSFT_SqlScript/MSFT_SqlScript.psm1 b/DSCResources/MSFT_SqlScript/MSFT_SqlScript.psm1 index e437b13c2..f2abc4936 100644 --- a/DSCResources/MSFT_SqlScript/MSFT_SqlScript.psm1 +++ b/DSCResources/MSFT_SqlScript/MSFT_SqlScript.psm1 @@ -1,5 +1,11 @@ -$script:currentPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent -Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script:currentPath -Parent) -Parent) -ChildPath 'SqlServerDscHelper.psm1') +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlScriptQuery/MSFT_SqlScriptQuery.psm1 b/DSCResources/MSFT_SqlScriptQuery/MSFT_SqlScriptQuery.psm1 index f788e2e02..6a88c1b54 100644 --- a/DSCResources/MSFT_SqlScriptQuery/MSFT_SqlScriptQuery.psm1 +++ b/DSCResources/MSFT_SqlScriptQuery/MSFT_SqlScriptQuery.psm1 @@ -1,5 +1,11 @@ -$script:currentPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent -Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script:currentPath -Parent) -Parent) -ChildPath 'SqlServerDscHelper.psm1') +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlServerConfiguration/MSFT_SqlServerConfiguration.psm1 b/DSCResources/MSFT_SqlServerConfiguration/MSFT_SqlServerConfiguration.psm1 index ce273a68b..789025a60 100644 --- a/DSCResources/MSFT_SqlServerConfiguration/MSFT_SqlServerConfiguration.psm1 +++ b/DSCResources/MSFT_SqlServerConfiguration/MSFT_SqlServerConfiguration.psm1 @@ -1,9 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlServerConfiguration' diff --git a/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.psm1 b/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.psm1 index efc7b7ac9..9c6bbacae 100644 --- a/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.psm1 +++ b/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.psm1 @@ -1,9 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlServerDatabaseMail' @@ -702,7 +704,7 @@ function Test-TargetResource if ($Ensure -eq 'Present') { - $returnValue = Test-SQLDscParameterState ` + $returnValue = Test-DscParameterState ` -CurrentValues $getTargetResourceResult ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @( diff --git a/DSCResources/MSFT_SqlServerEndpoint/MSFT_SqlServerEndpoint.psm1 b/DSCResources/MSFT_SqlServerEndpoint/MSFT_SqlServerEndpoint.psm1 index c55f2d9f3..927d250f2 100644 --- a/DSCResources/MSFT_SqlServerEndpoint/MSFT_SqlServerEndpoint.psm1 +++ b/DSCResources/MSFT_SqlServerEndpoint/MSFT_SqlServerEndpoint.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS Returns the current state of the endpoint. diff --git a/DSCResources/MSFT_SqlServerEndpointPermission/MSFT_SqlServerEndpointPermission.psm1 b/DSCResources/MSFT_SqlServerEndpointPermission/MSFT_SqlServerEndpointPermission.psm1 index 92f3b5af2..5c8b5a060 100644 --- a/DSCResources/MSFT_SqlServerEndpointPermission/MSFT_SqlServerEndpointPermission.psm1 +++ b/DSCResources/MSFT_SqlServerEndpointPermission/MSFT_SqlServerEndpointPermission.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS Returns the current state of the permissions for the principal (login). diff --git a/DSCResources/MSFT_SqlServerEndpointState/MSFT_SqlServerEndpointState.psm1 b/DSCResources/MSFT_SqlServerEndpointState/MSFT_SqlServerEndpointState.psm1 index 59b6bf88b..35df74894 100644 --- a/DSCResources/MSFT_SqlServerEndpointState/MSFT_SqlServerEndpointState.psm1 +++ b/DSCResources/MSFT_SqlServerEndpointState/MSFT_SqlServerEndpointState.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS Returns the current state of an endpoint. diff --git a/DSCResources/MSFT_SqlServerLogin/MSFT_SqlServerLogin.psm1 b/DSCResources/MSFT_SqlServerLogin/MSFT_SqlServerLogin.psm1 index 5d041fa33..1c7269d0f 100644 --- a/DSCResources/MSFT_SqlServerLogin/MSFT_SqlServerLogin.psm1 +++ b/DSCResources/MSFT_SqlServerLogin/MSFT_SqlServerLogin.psm1 @@ -1,6 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS @@ -434,24 +439,24 @@ function Test-TargetResource #> if ((Find-ExceptionByNumber -ExceptionToSearch $_.Exception -ErrorNumber 18470)) { - New-VerboseMessage -Message "Password valid, but '$Name' is disabled." + New-VerboseMessage -Message "Password valid, but '$Name' is disabled." } elseif ((Find-ExceptionByNumber -ExceptionToSearch $_.Exception -ErrorNumber 18456)) { New-VerboseMessage -Message $_.Exception.message - + # The password was not correct, password validation failed $testPassed = $false } - else + else { New-VerboseMessage -Message "Unknown error: $($_.Exception.message)" - + # Something else went wrong, rethrow error throw } } - else + else { New-VerboseMessage -Message "Password validation failed for the login '$Name'." $testPassed = $false diff --git a/DSCResources/MSFT_SqlServerMaxDop/MSFT_SqlServerMaxDop.psm1 b/DSCResources/MSFT_SqlServerMaxDop/MSFT_SqlServerMaxDop.psm1 index be82a50cb..e7f84fcc9 100644 --- a/DSCResources/MSFT_SqlServerMaxDop/MSFT_SqlServerMaxDop.psm1 +++ b/DSCResources/MSFT_SqlServerMaxDop/MSFT_SqlServerMaxDop.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS This function gets the max degree of parallelism server configuration option. diff --git a/DSCResources/MSFT_SqlServerMemory/MSFT_SqlServerMemory.psm1 b/DSCResources/MSFT_SqlServerMemory/MSFT_SqlServerMemory.psm1 index 6b15959e6..c792f7791 100644 --- a/DSCResources/MSFT_SqlServerMemory/MSFT_SqlServerMemory.psm1 +++ b/DSCResources/MSFT_SqlServerMemory/MSFT_SqlServerMemory.psm1 @@ -1,4 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'SqlServerDscHelper.psm1') -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlServerNetwork/MSFT_SqlServerNetwork.psm1 b/DSCResources/MSFT_SqlServerNetwork/MSFT_SqlServerNetwork.psm1 index 98da5b0e6..44d99e3a1 100644 --- a/DSCResources/MSFT_SqlServerNetwork/MSFT_SqlServerNetwork.psm1 +++ b/DSCResources/MSFT_SqlServerNetwork/MSFT_SqlServerNetwork.psm1 @@ -1,7 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') -Force -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') # Load localized string data $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlServerNetwork' diff --git a/DSCResources/MSFT_SqlServerPermission/MSFT_SqlServerPermission.psm1 b/DSCResources/MSFT_SqlServerPermission/MSFT_SqlServerPermission.psm1 index fbe9f3a99..e458479a7 100644 --- a/DSCResources/MSFT_SqlServerPermission/MSFT_SqlServerPermission.psm1 +++ b/DSCResources/MSFT_SqlServerPermission/MSFT_SqlServerPermission.psm1 @@ -1,6 +1,12 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') + <# .SYNOPSIS Returns the current state of the permissions for the principal (login). diff --git a/DSCResources/MSFT_SqlServerRole/MSFT_SqlServerRole.psm1 b/DSCResources/MSFT_SqlServerRole/MSFT_SqlServerRole.psm1 index 59ae0e17d..5cf77dd17 100644 --- a/DSCResources/MSFT_SqlServerRole/MSFT_SqlServerRole.psm1 +++ b/DSCResources/MSFT_SqlServerRole/MSFT_SqlServerRole.psm1 @@ -1,8 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlServerRole' diff --git a/DSCResources/MSFT_SqlServerSecureConnection/MSFT_SqlServerSecureConnection.psm1 b/DSCResources/MSFT_SqlServerSecureConnection/MSFT_SqlServerSecureConnection.psm1 index e14fd931c..6eb02e3a8 100644 --- a/DSCResources/MSFT_SqlServerSecureConnection/MSFT_SqlServerSecureConnection.psm1 +++ b/DSCResources/MSFT_SqlServerSecureConnection/MSFT_SqlServerSecureConnection.psm1 @@ -1,5 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlServerSecureConnection' diff --git a/DSCResources/MSFT_SqlServiceAccount/MSFT_SqlServiceAccount.psm1 b/DSCResources/MSFT_SqlServiceAccount/MSFT_SqlServiceAccount.psm1 index 463654577..65398dad8 100644 --- a/DSCResources/MSFT_SqlServiceAccount/MSFT_SqlServiceAccount.psm1 +++ b/DSCResources/MSFT_SqlServiceAccount/MSFT_SqlServiceAccount.psm1 @@ -1,8 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlServiceAccount' diff --git a/DSCResources/MSFT_SqlSetup/MSFT_SqlSetup.psm1 b/DSCResources/MSFT_SqlSetup/MSFT_SqlSetup.psm1 index e26c91724..25c123829 100644 --- a/DSCResources/MSFT_SqlSetup/MSFT_SqlSetup.psm1 +++ b/DSCResources/MSFT_SqlSetup/MSFT_SqlSetup.psm1 @@ -1,8 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath 'CommonResourceHelper.psm1') +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlSetup' @@ -89,13 +92,7 @@ function Get-TargetResource if ($SourceCredential) { - $newSmbMappingParameters = @{ - RemotePath = $SourcePath - UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" - Password = $($SourceCredential.GetNetworkCredential().Password) - } - - $null = New-SmbMapping @newSmbMappingParameters + Connect-UncPath -RemotePath $SourcePath -SourceCredential $SourceCredential } $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' @@ -106,7 +103,7 @@ function Get-TargetResource if ($SourceCredential) { - Remove-SmbMapping -RemotePath $SourcePath -Force + Disconnect-UncPath -RemotePath $SourcePath } if ($InstanceName -eq 'MSSQLSERVER') @@ -1043,24 +1040,14 @@ function Set-TargetResource 'UpdateSource' ) - # Remove trailing slash ('\') from paths + # Making sure paths are correct. foreach ($parameterName in $parametersToEvaluateTrailingSlash) { if ($PSBoundParameters.ContainsKey($parameterName)) { $parameterValue = Get-Variable -Name $parameterName -ValueOnly - - # Trim backslash, but only if the path contains a full path and not just a qualifier. - if ($parameterValue -and $parameterValue -notmatch '^[a-zA-Z]:\\$') - { - Set-Variable -Name $parameterName -Value $parameterValue.TrimEnd('\') - } - - # If the path only contains a qualifier but no backslash ('M:'), then a backslash is added ('M:\'). - if ($parameterValue -match '^[a-zA-Z]:$') - { - Set-Variable -Name $parameterName -Value "$parameterValue\" - } + $formattedPath = Format-Path -Path $parameterValue -TrailingSlash + Set-Variable -Name $parameterName -Value $formattedPath } } @@ -1068,29 +1055,13 @@ function Set-TargetResource if ($SourceCredential) { - $newSmbMappingParameters = @{ - RemotePath = $SourcePath - UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" - Password = $($SourceCredential.GetNetworkCredential().Password) + $invokeInstallationMediaCopyParameters = @{ + SourcePath = $SourcePath + SourceCredential = $SourceCredential + PassThru = $true } - $null = New-SmbMapping @newSmbMappingParameters - - # Create a destination folder so the media files aren't written to the root of the Temp folder. - $mediaDestinationFolder = Split-Path -Path $SourcePath -Leaf - if (-not $mediaDestinationFolder ) - { - $mediaDestinationFolder = New-Guid | Select-Object -ExpandProperty Guid - } - - $mediaDestinationPath = Join-Path -Path (Get-TemporaryFolder) -ChildPath $mediaDestinationFolder - - Write-Verbose -Message ($script:localizedData.RobocopyIsCopying -f $SourcePath, $mediaDestinationPath) - Copy-ItemWithRobocopy -Path $SourcePath -DestinationPath $mediaDestinationPath - - Remove-SmbMapping -RemotePath $SourcePath -Force - - $SourcePath = $mediaDestinationPath + $SourcePath = Invoke-InstallationMediaCopy @invokeInstallationMediaCopyParameters } $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' @@ -1101,20 +1072,23 @@ function Set-TargetResource # Determine features to install $featuresToInstall = '' - foreach ($feature in $Features.Split(',')) - { - # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase - $feature = $feature.ToUpper(); + $featuresArray = $Features -split ',' + + foreach ($feature in $featuresArray) + { if (($sqlVersion -in ('13','14')) -and ($feature -in ('ADV_SSMS','SSMS'))) { $errorMessage = $script:localizedData.FeatureNotSupported -f $feature New-InvalidOperationException -Message $errorMessage } - if (-not ($getTargetResourceResult.Features.Contains($feature))) + $foundFeaturesArray = $getTargetResourceResult.Features -split ',' + + if ($feature -notin $foundFeaturesArray) { - $featuresToInstall += "$feature," + # Must make sure the feature names are provided in upper-case. + $featuresToInstall += '{0},' -f $feature.ToUpper() } else { @@ -1577,13 +1551,13 @@ function Set-TargetResource # Logic added as a fix for Issue#1254 SqlSetup:Fails when a root directory is specified if($currentSetupArgument.Value -match '^[a-zA-Z]:\\$') { - $setupArgumentValue = $currentSetupArgument.Value + $setupArgumentValue = $currentSetupArgument.Value } - else + else { - $setupArgumentValue = '"{0}"' -f $currentSetupArgument.Value + $setupArgumentValue = '"{0}"' -f $currentSetupArgument.Value } - + } } @@ -1654,7 +1628,7 @@ function Set-TargetResource Write-Verbose -Message $setupExitMessageSuccessful } - if ($ForceReboot -or ($null -ne (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue))) + if ($ForceReboot -or (Test-PendingRestart)) { if (-not ($SuppressReboot)) { @@ -2142,18 +2116,25 @@ function Test-TargetResource $boundParameters = $PSBoundParameters $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - Write-Verbose -Message ($script:localizedData.FeaturesFound -f $($getTargetResourceResult.Features)) + if ($null -eq $getTargetResourceResult.Features -or $getTargetResourceResult.Features -eq '') + { + Write-Verbose -Message $script:localizedData.NoFeaturesFound + } + else + { + Write-Verbose -Message ($script:localizedData.FeaturesFound -f $getTargetResourceResult.Features) + } $result = $true - if ($getTargetResourceResult.Features ) + if ($getTargetResourceResult.Features) { - foreach ($feature in $Features.Split(",")) - { - # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase - $feature = $feature.ToUpper(); + $featuresArray = $Features -split ',' + $foundFeaturesArray = $getTargetResourceResult.Features -split ',' - if(!($getTargetResourceResult.Features.Contains($feature))) + foreach ($feature in $featuresArray) + { + if ($feature -notin $foundFeaturesArray) { Write-Verbose -Message ($script:localizedData.UnableToFindFeature -f $feature, $($getTargetResourceResult.Features)) $result = $false @@ -2233,114 +2214,6 @@ function Get-FirstItemPropertyValue return $registryPropertyValue } -<# - .SYNOPSIS - Copy folder structure using Robocopy. Every file and folder, including empty ones are copied. - - .PARAMETER Path - Source path to be copied. - - .PARAMETER DestinationPath - The path to the destination. -#> -function Copy-ItemWithRobocopy -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Path, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $DestinationPath - ) - $quotedPath = '"{0}"' -f $Path - $quotedDestinationPath = '"{0}"' -f $DestinationPath - $robocopyExecutable = Get-Command -Name "Robocopy.exe" -ErrorAction Stop - - $robocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' - $robocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' - $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' - - if ([System.Version]$robocopyExecutable.FileVersionInfo.ProductVersion -ge [System.Version]'6.3.9600.16384') - { - Write-Verbose -Message $script:localizedData.RobocopyUsingUnbufferedIo - - $robocopyArgumentUseUnbufferedIO = '/J' - } - else - { - Write-Verbose -Message $script:localizedData.RobocopyNotUsingUnbufferedIo - } - - $robocopyArgumentList = '{0} {1} {2} {3} {4} {5}' -f $quotedPath, - $quotedDestinationPath, - $robocopyArgumentCopySubDirectoriesIncludingEmpty, - $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, - $robocopyArgumentUseUnbufferedIO, - $robocopyArgumentSilent - - $robocopyStartProcessParameters = @{ - FilePath = $robocopyExecutable.Name - ArgumentList = $robocopyArgumentList - } - - Write-Verbose -Message ($script:localizedData.RobocopyArguments -f $robocopyArgumentList ) - $robocopyProcess = Start-Process @robocopyStartProcessParameters -Wait -NoNewWindow -PassThru - - switch ($($robocopyProcess.ExitCode)) - { - {$_ -in 8, 16} - { - $errorMessage = $script:localizedData.RobocopyErrorCopying -f $_ - New-InvalidOperationException -Message $errorMessage - } - - {$_ -gt 7 } - { - $errorMessage = $script:localizedData.RobocopyFailuresCopying -f $_ - New-InvalidResultException -Message $errorMessage - } - - 1 - { - Write-Verbose -Message $script:localizedData.RobocopySuccessful - } - - 2 - { - Write-Verbose -Message $script:localizedData.RobocopyRemovedExtraFilesAtDestination - } - - 3 - { - Write-Verbose -Message $script:localizedData.RobocopySuccessfulAndRemovedExtraFilesAtDestination - } - - {$_ -eq 0 -or $null -eq $_ } - { - Write-Verbose -Message $script:localizedData.RobocopyAllFilesPresent - } - } -} - -<# - .SYNOPSIS - Returns the path of the current user's temporary folder. -#> -function Get-TemporaryFolder -{ - [CmdletBinding()] - [OutputType([System.String])] - param() - - return [IO.Path]::GetTempPath() -} - <# .SYNOPSIS Returns the decimal representation of an IP Addresses. @@ -2455,50 +2328,6 @@ function Get-ServiceAccountParameters return $parameters } -<# - .SYNOPSIS - Starts the SQL setup process. - - .PARAMETER FilePath - String containing the path to setup.exe. - - .PARAMETER ArgumentList - The arguments that should be passed to setup.exe. - - .PARAMETER Timeout - The timeout in seconds to wait for the process to finish. -#> -function Start-SqlSetupProcess -{ - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $FilePath, - - [Parameter()] - [System.String] - $ArgumentList, - - [Parameter(Mandatory = $true)] - [System.UInt32] - $Timeout - ) - - $startProcessParameters = @{ - FilePath = $FilePath - ArgumentList = $ArgumentList - } - - $sqlSetupProcess = Start-Process @startProcessParameters -PassThru -NoNewWindow -ErrorAction Stop - - Write-Verbose -Message ($script:localizedData.StartSetupProcess -f $sqlSetupProcess.Id, $startProcessParameters.FilePath, $Timeout) - - Wait-Process -InputObject $sqlSetupProcess -Timeout $Timeout -ErrorAction Stop - - return $sqlSetupProcess.ExitCode -} - <# .SYNOPSIS Converts the start mode property returned by a Win32_Service CIM object to the resource properties *StartupType equivalent diff --git a/DSCResources/MSFT_SqlSetup/en-US/MSFT_SqlSetup.strings.psd1 b/DSCResources/MSFT_SqlSetup/en-US/MSFT_SqlSetup.strings.psd1 index ca1cd0878..fbce73d37 100644 --- a/DSCResources/MSFT_SqlSetup/en-US/MSFT_SqlSetup.strings.psd1 +++ b/DSCResources/MSFT_SqlSetup/en-US/MSFT_SqlSetup.strings.psd1 @@ -57,20 +57,11 @@ ConvertFrom-StringData @' Reboot = Rebooting target node. SuppressReboot = Suppressing reboot of target node. TestFailedAfterSet = Test-TargetResource returned false after calling Set-TargetResource. - FeaturesFound = Features found: {0} + FeaturesFound = Found features already installed: {0} + NoFeaturesFound = No features are installed. UnableToFindFeature = Unable to find feature '{0}' among the installed features: '{1}'. EvaluatingClusterParameters = Clustered install, checking parameters. ClusterParameterIsNotInDesiredState = {0} '{1}' is not in the desired state for this cluster. - RobocopyUsingUnbufferedIo = Robocopy is using unbuffered I/O. - RobocopyNotUsingUnbufferedIo = Unbuffered I/O cannot be used due to incompatible version of Robocopy. - RobocopyArguments = Robocopy is started with the following arguments: {0} - RobocopyErrorCopying = Robocopy reported errors when copying files. Error code: {0}. - RobocopyFailuresCopying = Robocopy reported that failures occurred when copying files. Error code: {0}. - RobocopySuccessful = Robocopy copied files successfully - RobocopyRemovedExtraFilesAtDestination = Robocopy found files at the destination path that is not present at the source path, these extra files was remove at the destination path. - RobocopySuccessfulAndRemovedExtraFilesAtDestination = Robocopy copied files to destination successfully. Robocopy also found files at the destination path that is not present at the source path, these extra files was remove at the destination path. - RobocopyAllFilesPresent = Robocopy reported that all files already present. - StartSetupProcess = Started the process with id {0} using the path '{1}', and with a timeout value of {2} seconds. EvaluateMasterDataServicesFeature = Detecting Master Data Services (MDS) feature ({0}). MasterDataServicesFeatureFound = Master Data Services (MDS) feature detected. MasterDataServicesFeatureNotFound = Master Data Services (MDS) feature not detected. diff --git a/DSCResources/MSFT_SqlWaitForAG/MSFT_SqlWaitForAG.psm1 b/DSCResources/MSFT_SqlWaitForAG/MSFT_SqlWaitForAG.psm1 index 253dd405e..24b52a835 100644 --- a/DSCResources/MSFT_SqlWaitForAG/MSFT_SqlWaitForAG.psm1 +++ b/DSCResources/MSFT_SqlWaitForAG/MSFT_SqlWaitForAG.psm1 @@ -1,6 +1,11 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'SqlServerDscHelper.psm1') ` - -Force +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/DSCResources/MSFT_SqlWindowsFirewall/MSFT_SqlWindowsFirewall.psm1 b/DSCResources/MSFT_SqlWindowsFirewall/MSFT_SqlWindowsFirewall.psm1 index 40ac00119..513fe8e8d 100644 --- a/DSCResources/MSFT_SqlWindowsFirewall/MSFT_SqlWindowsFirewall.psm1 +++ b/DSCResources/MSFT_SqlWindowsFirewall/MSFT_SqlWindowsFirewall.psm1 @@ -1,5 +1,11 @@ -$script:currentPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent -Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script:currentPath -Parent) -Parent) -ChildPath 'SqlServerDscHelper.psm1') +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' + +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + +$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common' +Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1') <# .SYNOPSIS diff --git a/Examples/README.md b/Examples/README.md index 7bd74297a..9917443eb 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -21,6 +21,7 @@ These are the links to the examples for each individual resource. - [SqlDatabaseRecoveryModel](Resources/SqlDatabaseRecoveryModel) - [SqlDatabaseRole](Resources/SqlDatabaseRole) - [SqlRS](Resources/SqlRS) +- [SqlRSSetup](Resources/SqlRSSetup) - [SqlScript](Resources/SqlScript) - [SqlScriptQuery](Resources/SqlScriptQuery) - [SqlServerConfiguration](Resources/SqlServerConfiguration) diff --git a/Examples/Resources/SqlRSSetup/1-InstallReportingServices.ps1 b/Examples/Resources/SqlRSSetup/1-InstallReportingServices.ps1 new file mode 100644 index 000000000..da99e9f55 --- /dev/null +++ b/Examples/Resources/SqlRSSetup/1-InstallReportingServices.ps1 @@ -0,0 +1,31 @@ +<# + .EXAMPLE + This example shows how to install a Microsoft SQL Server Reporting Service + instance (2017 or newer). +#> +Configuration Example +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + $SqlInstallCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlRSSetup 'InstallDefaultInstance' + { + InstanceName = 'SSRS' + IAcceptLicenseTerms = 'Yes' + SourcePath = 'C:\InstallMedia\SQLServerReportingServices.exe' + Edition = 'Development' + + PsDscRunAsCredential = $SqlInstallCredential + } + } +} diff --git a/Examples/Resources/SqlRSSetup/2-UninstallReportingServices.ps1 b/Examples/Resources/SqlRSSetup/2-UninstallReportingServices.ps1 new file mode 100644 index 000000000..4c7a4f61a --- /dev/null +++ b/Examples/Resources/SqlRSSetup/2-UninstallReportingServices.ps1 @@ -0,0 +1,33 @@ +<# + .EXAMPLE + This example shows how to install a Microsoft SQL Server Reporting Service + instance (2017 or newer). +#> +Configuration Example +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + $SqlInstallCredential + ) + + Import-DscResource -ModuleName SqlServerDsc + + node localhost + { + SqlRSSetup 'InstallDefaultInstance' + { + InstanceName = 'SSRS' + SourcePath = 'C:\InstallMedia\SQLServerReportingServices.exe' + Action = 'Uninstall' + + # This needs to be set to although it is not used during uninstall. + IAcceptLicenseTerms = 'Yes' + + PsDscRunAsCredential = $SqlInstallCredential + } + } +} diff --git a/SqlServerDscHelper.psm1 b/Modules/DscResource.Common/DscResource.Common.psm1 similarity index 79% rename from SqlServerDscHelper.psm1 rename to Modules/DscResource.Common/DscResource.Common.psm1 index 8f3f682fc..203a37586 100644 --- a/SqlServerDscHelper.psm1 +++ b/Modules/DscResource.Common/DscResource.Common.psm1 @@ -1,9 +1,607 @@ -# Load Localization Data -Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot ` - -ChildPath 'DscResources') ` - -ChildPath 'CommonResourceHelper.psm1') +$script:modulesFolderPath = Split-Path -Path $PSScriptRoot -Parent -$script:localizedData = Get-LocalizedData -ResourceName 'SqlServerDscHelper' -ScriptRoot $PSScriptRoot +$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper' +Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1') + + +$script:localizedData = Get-LocalizedData -ResourceName 'DscResource.Common' -ScriptRoot $PSScriptRoot + +<# + .SYNOPSIS + This method is used to compare current and desired values for any DSC resource. + + .PARAMETER CurrentValues + This is hash table of the current values that are applied to the resource. + + .PARAMETER DesiredValues + This is a PSBoundParametersDictionary of the desired values for the resource. + + .PARAMETER ValuesToCheck + This is a list of which properties in the desired values list should be checked. + If this is empty then all values in DesiredValues are checked. +#> +function Test-DscParameterState +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $CurrentValues, + + [Parameter(Mandatory = $true)] + [System.Object] + $DesiredValues, + + [Parameter()] + [System.Array] + $ValuesToCheck + ) + + $returnValue = $true + + if (($DesiredValues.GetType().Name -ne 'HashTable') ` + -and ($DesiredValues.GetType().Name -ne 'CimInstance') ` + -and ($DesiredValues.GetType().Name -ne 'PSBoundParametersDictionary')) + { + $errorMessage = $script:localizedData.PropertyTypeInvalidForDesiredValues -f $($DesiredValues.GetType().Name) + New-InvalidArgumentException -ArgumentName 'DesiredValues' -Message $errorMessage + } + + if (($DesiredValues.GetType().Name -eq 'CimInstance') -and ($null -eq $ValuesToCheck)) + { + $errorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck + New-InvalidArgumentException -ArgumentName 'ValuesToCheck' -Message $errorMessage + } + + if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) + { + $keyList = $DesiredValues.Keys + } + else + { + $keyList = $ValuesToCheck + } + + $keyList | ForEach-Object -Process { + if (($_ -ne 'Verbose')) + { + if (($CurrentValues.ContainsKey($_) -eq $false) ` + -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` + -or (($DesiredValues.GetType().Name -ne 'CimInstance' -and $DesiredValues.ContainsKey($_) -eq $true) -and ($null -ne $DesiredValues.$_ -and $DesiredValues.$_.GetType().IsArray))) + { + if ($DesiredValues.GetType().Name -eq 'HashTable' -or ` + $DesiredValues.GetType().Name -eq 'PSBoundParametersDictionary') + { + $checkDesiredValue = $DesiredValues.ContainsKey($_) + } + else + { + # If DesiredValue is a CimInstance. + $checkDesiredValue = $false + if (([System.Boolean]($DesiredValues.PSObject.Properties.Name -contains $_)) -eq $true) + { + if ($null -ne $DesiredValues.$_) + { + $checkDesiredValue = $true + } + } + } + + if ($checkDesiredValue) + { + $desiredType = $DesiredValues.$_.GetType() + $fieldName = $_ + if ($desiredType.IsArray -eq $true) + { + if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` + -or ($null -eq $CurrentValues.$fieldName)) + { + Write-Verbose -Message ($script:localizedData.PropertyValidationError -f $fieldName) -Verbose + + $returnValue = $false + } + else + { + $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` + -DifferenceObject $DesiredValues.$fieldName + if ($null -ne $arrayCompare) + { + Write-Verbose -Message ($script:localizedData.PropertiesDoesNotMatch -f $fieldName) -Verbose + + $arrayCompare | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.PropertyThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose + } + + $returnValue = $false + } + } + } + else + { + switch ($desiredType.Name) + { + 'String' + { + if (-not [System.String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` + -not [System.String]::IsNullOrEmpty($DesiredValues.$fieldName)) + { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + 'Int32' + { + if (-not ($DesiredValues.$fieldName -eq 0) -or ` + -not ($null -eq $CurrentValues.$fieldName)) + { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + { $_ -eq 'Int16' -or $_ -eq 'UInt16' -or $_ -eq 'Single' } + { + if (-not ($DesiredValues.$fieldName -eq 0) -or ` + -not ($null -eq $CurrentValues.$fieldName)) + { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + 'Boolean' + { + if ($CurrentValues.$fieldName -ne $DesiredValues.$fieldName) + { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + default + { + Write-Warning -Message ($script:localizedData.UnableToCompareProperty ` + -f $fieldName, $desiredType.Name) + + $returnValue = $false + } + } + } + } + } + } + } + + return $returnValue +} + +<# + .SYNOPSIS + Returns the value of the provided in the Name parameter, at the registry + location provided in the Path parameter. + + .PARAMETER Path + String containing the path in the registry to the property name. + + .PARAMETER PropertyName + String containing the name of the property for which the value is returned. +#> +function Get-RegistryPropertyValue +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Path, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $getItemPropertyParameters = @{ + Path = $Path + Name = $Name + } + + <# + Using a try/catch block instead of 'SilentlyContinue' to be + able to unit test a failing registry path. + #> + try + { + $getItemPropertyResult = (Get-ItemProperty @getItemPropertyParameters -ErrorAction Stop).$Name + } + catch + { + $getItemPropertyResult = $null + } + + return $getItemPropertyResult +} + +<# + .SYNOPSIS + Returns the value of the provided in the Name parameter, at the registry + location provided in the Path parameter. + + .PARAMETER Path + String containing the path in the registry to the property name. + + .PARAMETER PropertyName + String containing the name of the property for which the value is returned. +#> +function Format-Path +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Path, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $TrailingSlash + ) + + # Remove trailing slash ('\') from path. + if ($TrailingSlash.IsPresent) + { + <# + Trim backslash, but only if the path contains a full path and + not just a qualifier. + #> + if ($Path -notmatch '^[a-zA-Z]:\\$') + { + $Path = $Path.TrimEnd('\') + } + + <# + If the path only contains a qualifier but no backslash ('M:'), + then a backslash is added ('M:\'). + #> + if ($Path -match '^[a-zA-Z]:$') + { + $Path = '{0}\' -f $Path + } + } + + return $Path +} + +<# + .SYNOPSIS + Copy folder structure using Robocopy. Every file and folder, including empty ones are copied. + + .PARAMETER Path + Source path to be copied. + + .PARAMETER DestinationPath + The path to the destination. +#> +function Copy-ItemWithRobocopy +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Path, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DestinationPath + ) + + $quotedPath = '"{0}"' -f $Path + $quotedDestinationPath = '"{0}"' -f $DestinationPath + $robocopyExecutable = Get-Command -Name "Robocopy.exe" -ErrorAction Stop + + $robocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' + $robocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' + $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' + + if ([System.Version]$robocopyExecutable.FileVersionInfo.ProductVersion -ge [System.Version]'6.3.9600.16384') + { + Write-Verbose -Message $script:localizedData.RobocopyUsingUnbufferedIo -Verbose + + $robocopyArgumentUseUnbufferedIO = '/J' + } + else + { + Write-Verbose -Message $script:localizedData.RobocopyNotUsingUnbufferedIo -Verbose + } + + $robocopyArgumentList = '{0} {1} {2} {3} {4} {5}' -f $quotedPath, + $quotedDestinationPath, + $robocopyArgumentCopySubDirectoriesIncludingEmpty, + $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, + $robocopyArgumentUseUnbufferedIO, + $robocopyArgumentSilent + + $robocopyStartProcessParameters = @{ + FilePath = $robocopyExecutable.Name + ArgumentList = $robocopyArgumentList + } + + Write-Verbose -Message ($script:localizedData.RobocopyArguments -f $robocopyArgumentList) -Verbose + $robocopyProcess = Start-Process @robocopyStartProcessParameters -Wait -NoNewWindow -PassThru + + switch ($($robocopyProcess.ExitCode)) + { + {$_ -in 8, 16} + { + $errorMessage = $script:localizedData.RobocopyErrorCopying -f $_ + New-InvalidOperationException -Message $errorMessage + } + + {$_ -gt 7 } + { + $errorMessage = $script:localizedData.RobocopyFailuresCopying -f $_ + New-InvalidResultException -Message $errorMessage + } + + 1 + { + Write-Verbose -Message $script:localizedData.RobocopySuccessful -Verbose + } + + 2 + { + Write-Verbose -Message $script:localizedData.RobocopyRemovedExtraFilesAtDestination -Verbose + } + + 3 + { + Write-Verbose -Message $script:localizedData.RobocopySuccessfulAndRemovedExtraFilesAtDestination -Verbose + } + + {$_ -eq 0 -or $null -eq $_ } + { + Write-Verbose -Message $script:localizedData.RobocopyAllFilesPresent -Verbose + } + } +} + +<# + .SYNOPSIS + Returns the path of the current user's temporary folder. +#> +function Get-TemporaryFolder +{ + [CmdletBinding()] + [OutputType([System.String])] + param() + + return [IO.Path]::GetTempPath() +} + +<# + .SYNOPSIS + Connects to the source using the provided credentials and then uses + robocopy to download the installation media to a local temporary folder. + + .PARAMETER SourcePath + Source path to be copied. + + .PARAMETER SourceCredential + The credentials to access the SourcePath. + + .PARAMETER PassThru + If used, returns the destination path as string. + + .OUTPUTS + Returns the destination path (when used with the parameter PassThru). +#> +function Invoke-InstallationMediaCopy +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $SourcePath, + + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SourceCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru + ) + + Connect-UncPath -RemotePath $SourcePath -SourceCredential $SourceCredential + + <# + Create a destination folder so the media files aren't written + to the root of the Temp folder. + #> + $mediaDestinationFolder = Split-Path -Path $SourcePath -Leaf + if (-not $mediaDestinationFolder ) + { + $mediaDestinationFolder = New-Guid | Select-Object -ExpandProperty Guid + } + + $mediaDestinationPath = Join-Path -Path (Get-TemporaryFolder) -ChildPath $mediaDestinationFolder + + Write-Verbose -Message ($script:localizedData.RobocopyIsCopying -f $SourcePath, $mediaDestinationPath) + Copy-ItemWithRobocopy -Path $SourcePath -DestinationPath $mediaDestinationPath + + Disconnect-UncPath -RemotePath $SourcePath + + if ($PassThru.IsPresent) + { + return $mediaDestinationPath + } +} + +<# + .SYNOPSIS + Connects to the UNC path provided in the parameter SourcePath. + Optionally connects using the provided credentials. + + .PARAMETER SourcePath + Source path to connect to. + + .PARAMETER SourceCredential + The credentials to access the path provided in SourcePath. + + .PARAMETER PassThru + If used, returns a MSFT_SmbMapping object that represents the newly + created SMB mapping. + + .OUTPUTS + Returns a MSFT_SmbMapping object that represents the newly created + SMB mapping (ony when used with parameter PassThru). +#> +function Connect-UncPath +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RemotePath, + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PassThru + ) + + $newSmbMappingParameters = @{ + RemotePath = $RemotePath + } + + if ($PSBoundParameters.ContainsKey('SourceCredential')) + { + $newSmbMappingParameters['UserName'] = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + $newSmbMappingParameters['Password'] = $SourceCredential.GetNetworkCredential().Password + } + + $newSmbMappingResult = New-SmbMapping @newSmbMappingParameters + + if ($PassThru.IsPresent) + { + return $newSmbMappingResult + } +} + +<# + .SYNOPSIS + Disconnects from the UNC path provided in the parameter SourcePath. + + .PARAMETER SourcePath + Source path to disconnect from. +#> +function Disconnect-UncPath +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RemotePath + ) + + Remove-SmbMapping -RemotePath $RemotePath -Force +} + +<# + .SYNOPSIS + Queries the registry and returns $true if there is a pending reboot. + + .OUTPUTS + Returns $true if there is a pending reboot, otherwise it returns $false. +#> +function Test-PendingRestart +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + ) + + $getRegistryPropertyValueParameters = @{ + Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' + Name = 'PendingFileRenameOperations' + } + + <# + If the key 'PendingFileRenameOperations' does not exist then if should + return $false, otherwise it should return $true. + #> + return $null -ne (Get-RegistryPropertyValue @getRegistryPropertyValueParameters) +} + +<# + .SYNOPSIS + Starts the SQL setup process. + + .PARAMETER FilePath + String containing the path to setup.exe. + + .PARAMETER ArgumentList + The arguments that should be passed to setup.exe. + + .PARAMETER Timeout + The timeout in seconds to wait for the process to finish. +#> +function Start-SqlSetupProcess +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $FilePath, + + [Parameter()] + [System.String] + $ArgumentList, + + [Parameter(Mandatory = $true)] + [System.UInt32] + $Timeout + ) + + $startProcessParameters = @{ + FilePath = $FilePath + ArgumentList = $ArgumentList + } + + $sqlSetupProcess = Start-Process @startProcessParameters -PassThru -NoNewWindow -ErrorAction Stop + + Write-Verbose -Message ($script:localizedData.StartSetupProcess -f $sqlSetupProcess.Id, $startProcessParameters.FilePath, $Timeout) -Verbose + + Wait-Process -InputObject $sqlSetupProcess -Timeout $Timeout -ErrorAction Stop + + return $sqlSetupProcess.ExitCode +} <# .SYNOPSIS @@ -408,174 +1006,6 @@ function New-VerboseMessage Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": $Message") -Verbose } -<# - .SYNOPSIS - This method is used to compare current and desired values for any DSC resource. - - .PARAMETER CurrentValues - This is hash table of the current values that are applied to the resource. - - .PARAMETER DesiredValues - This is a PSBoundParametersDictionary of the desired values for the resource. - - .PARAMETER ValuesToCheck - This is a list of which properties in the desired values list should be checked. - If this is empty then all values in DesiredValues are checked. -#> -function Test-SQLDscParameterState -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.Collections.Hashtable] - $CurrentValues, - - [Parameter(Mandatory = $true)] - [System.Object] - $DesiredValues, - - [Parameter()] - [System.Array] - $ValuesToCheck - ) - - $returnValue = $true - - if (($DesiredValues.GetType().Name -ne 'HashTable') ` - -and ($DesiredValues.GetType().Name -ne 'CimInstance') ` - -and ($DesiredValues.GetType().Name -ne 'PSBoundParametersDictionary')) - { - $errorMessage = $script:localizedData.PropertyTypeInvalidForDesiredValues -f $($DesiredValues.GetType().Name) - New-InvalidArgumentException -ArgumentName 'DesiredValues' -Message $errorMessage - } - - if (($DesiredValues.GetType().Name -eq 'CimInstance') -and ($null -eq $ValuesToCheck)) - { - $errorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck - New-InvalidArgumentException -ArgumentName 'ValuesToCheck' -Message $errorMessage - } - - if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) - { - $keyList = $DesiredValues.Keys - } - else - { - $keyList = $ValuesToCheck - } - - $keyList | ForEach-Object -Process { - if (($_ -ne 'Verbose')) - { - if (($CurrentValues.ContainsKey($_) -eq $false) ` - -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` - -or (($DesiredValues.GetType().Name -ne 'CimInstance' -and $DesiredValues.ContainsKey($_) -eq $true) -and ($null -ne $DesiredValues.$_ -and $DesiredValues.$_.GetType().IsArray))) - { - if ($DesiredValues.GetType().Name -eq 'HashTable' -or ` - $DesiredValues.GetType().Name -eq 'PSBoundParametersDictionary') - { - $checkDesiredValue = $DesiredValues.ContainsKey($_) - } - else - { - # If DesiredValue is a CimInstance. - $checkDesiredValue = $false - if (([System.Boolean]($DesiredValues.PSObject.Properties.Name -contains $_)) -eq $true) - { - if ($null -ne $DesiredValues.$_) - { - $checkDesiredValue = $true - } - } - } - - if ($checkDesiredValue) - { - $desiredType = $DesiredValues.$_.GetType() - $fieldName = $_ - if ($desiredType.IsArray -eq $true) - { - if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` - -or ($null -eq $CurrentValues.$fieldName)) - { - Write-Verbose -Message ($script:localizedData.PropertyValidationError -f $fieldName) -Verbose - - $returnValue = $false - } - else - { - $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` - -DifferenceObject $DesiredValues.$fieldName - if ($null -ne $arrayCompare) - { - Write-Verbose -Message ($script:localizedData.PropertiesDoesNotMatch -f $fieldName) -Verbose - - $arrayCompare | ForEach-Object -Process { - Write-Verbose -Message ($script:localizedData.PropertyThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose - } - - $returnValue = $false - } - } - } - else - { - switch ($desiredType.Name) - { - 'String' - { - if (-not [System.String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` - -not [System.String]::IsNullOrEmpty($DesiredValues.$fieldName)) - { - Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` - -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose - - $returnValue = $false - } - } - - 'Int32' - { - if (-not ($DesiredValues.$fieldName -eq 0) -or ` - -not ($null -eq $CurrentValues.$fieldName)) - { - Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` - -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose - - $returnValue = $false - } - } - - { $_ -eq 'Int16' -or $_ -eq 'UInt16'} - { - if (-not ($DesiredValues.$fieldName -eq 0) -or ` - -not ($null -eq $CurrentValues.$fieldName)) - { - Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` - -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose - - $returnValue = $false - } - } - - default - { - Write-Warning -Message ($script:localizedData.UnableToCompareProperty ` - -f $fieldName, $desiredType.Name) - - $returnValue = $false - } - } - } - } - } - } - } - - return $returnValue -} - <# .SYNOPSIS Imports the module SQLPS in a standardized way. @@ -1118,8 +1548,8 @@ function Test-LoginEffectivePermissions [System.String[]] $Permissions, - [ValidateSet('SERVER', 'LOGIN')] [Parameter()] + [ValidateSet('SERVER', 'LOGIN')] [System.String] $SecurableClass = 'SERVER', @@ -1346,7 +1776,8 @@ function Test-ImpersonatePermissions New-VerboseMessage -Message ( 'The login "{0}" does not have control server permissions on the instance "{1}\{2}".' -f $testLoginEffectivePermissionsParams.LoginName, $testLoginEffectivePermissionsParams.SQLServer, $testLoginEffectivePermissionsParams.SQLInstanceName ) } - if ( -not [System.String]::IsNullOrEmpty($SecurableName) ) { + if (-not [System.String]::IsNullOrEmpty($SecurableName)) + { # Check for login-specific impersonation permissions $testLoginEffectivePermissionsParams = @{ SQLServer = $ServerObject.ComputerNamePhysicalNetBIOS @@ -1356,6 +1787,7 @@ function Test-ImpersonatePermissions SecurableClass = 'LOGIN' SecurableName = $SecurableName } + $impersonatePermissionsPresent = Test-LoginEffectivePermissions @testLoginEffectivePermissionsParams if ($impersonatePermissionsPresent) { @@ -1376,6 +1808,7 @@ function Test-ImpersonatePermissions SecurableClass = 'LOGIN' SecurableName = $SecurableName } + $impersonatePermissionsPresent = Test-LoginEffectivePermissions @testLoginEffectivePermissionsParams if ($impersonatePermissionsPresent) { @@ -1402,7 +1835,7 @@ function Test-ImpersonatePermissions .OUTPUTS Hash table with the properties SQLServer and SQLInstanceName. #> -function Split-FullSQLInstanceName +function Split-FullSqlInstanceName { param ( @@ -1736,3 +2169,36 @@ function Find-ExceptionByNumber return $errorFound } +Export-ModuleMember -Function @( + 'Test-DscParameterState' + 'Get-RegistryPropertyValue' + 'Format-Path' + 'Copy-ItemWithRobocopy' + 'Get-TemporaryFolder' + 'Invoke-InstallationMediaCopy' + 'Connect-UncPath' + 'Disconnect-UncPath' + 'Test-PendingRestart' + 'Start-SqlSetupProcess' + 'Connect-SQL' + 'Connect-SQLAnalysis' + 'Get-SqlInstanceMajorVersion' + 'New-TerminatingError' + 'New-WarningMessage' + 'New-VerboseMessage' + 'Import-SQLPSModule' + 'Restart-SqlService' + 'Restart-ReportingServicesService' + 'Invoke-Query' + 'Update-AvailabilityGroupReplica' + 'Test-LoginEffectivePermissions' + 'Test-AvailabilityReplicaSeedingModeAutomatic' + 'Get-PrimaryReplicaServerObject' + 'Test-ImpersonatePermissions' + 'Split-FullSqlInstanceName' + 'Test-ClusterPermissions' + 'Test-ActiveNode' + 'Invoke-SqlScript' + 'Get-ServiceAccount' + 'Find-ExceptionByNumber' +) diff --git a/en-US/SqlServerDscHelper.strings.psd1 b/Modules/DscResource.Common/en-US/DscResource.Common.strings.psd1 similarity index 89% rename from en-US/SqlServerDscHelper.strings.psd1 rename to Modules/DscResource.Common/en-US/DscResource.Common.strings.psd1 index 71acf9033..c0b589dc1 100644 --- a/en-US/SqlServerDscHelper.strings.psd1 +++ b/Modules/DscResource.Common/en-US/DscResource.Common.strings.psd1 @@ -1,19 +1,29 @@ -# Localized resources for helper module SqlServerDscHelper. +# Localized resources for helper module DscResource.Common. ConvertFrom-StringData @' - ConnectedToDatabaseEngineInstance = Connected to SQL instance '{0}'. - FailedToConnectToDatabaseEngineInstance = Failed to connect to SQL instance '{0}'. - ConnectedToAnalysisServicesInstance = Connected to Analysis Services instance '{0}'. - FailedToConnectToAnalysisServicesInstance = Failed to connected to Analysis Services instance '{0}'. - SqlMajorVersion = SQL major version is {0}. - SqlServerVersionIsInvalid = Could not get the SQL version for the instance '{0}'. PropertyTypeInvalidForDesiredValues = Property 'DesiredValues' must be either a [System.Collections.Hashtable], [CimInstance] or [PSBoundParametersDictionary]. The type detected was {0}. PropertyTypeInvalidForValuesToCheck = If 'DesiredValues' is a CimInstance, then property 'ValuesToCheck' must contain a value. PropertyValidationError = Expected to find an array value for property {0} in the current values, but it was either not present or was null. This has caused the test method to return false. PropertiesDoesNotMatch = Found an array for property {0} in the current values, but this array does not match the desired state. Details of the changes are below. PropertyThatDoesNotMatch = {0} - {1} ValueOfTypeDoesNotMatch = {0} value for property {1} does not match. Current state is '{2}' and desired state is '{3}'. - UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-SQLDSCParameterState cmdlet. + UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-DscParameterState cmdlet. + RobocopyUsingUnbufferedIo = Robocopy is using unbuffered I/O. + RobocopyNotUsingUnbufferedIo = Unbuffered I/O cannot be used due to incompatible version of Robocopy. + RobocopyArguments = Robocopy is started with the following arguments: {0} + RobocopyErrorCopying = Robocopy reported errors when copying files. Error code: {0}. + RobocopyFailuresCopying = Robocopy reported that failures occurred when copying files. Error code: {0}. + RobocopySuccessful = Robocopy copied files successfully + RobocopyRemovedExtraFilesAtDestination = Robocopy found files at the destination path that is not present at the source path, these extra files was remove at the destination path. + RobocopySuccessfulAndRemovedExtraFilesAtDestination = Robocopy copied files to destination successfully. Robocopy also found files at the destination path that is not present at the source path, these extra files was remove at the destination path. + RobocopyAllFilesPresent = Robocopy reported that all files already present. + StartSetupProcess = Started the process with id {0} using the path '{1}', and with a timeout value of {2} seconds. + ConnectedToDatabaseEngineInstance = Connected to SQL instance '{0}'. + FailedToConnectToDatabaseEngineInstance = Failed to connect to SQL instance '{0}'. + ConnectedToAnalysisServicesInstance = Connected to Analysis Services instance '{0}'. + FailedToConnectToAnalysisServicesInstance = Failed to connected to Analysis Services instance '{0}'. + SqlMajorVersion = SQL major version is {0}. + SqlServerVersionIsInvalid = Could not get the SQL version for the instance '{0}'. PreferredModuleFound = Preferred module SqlServer found. PreferredModuleNotFound = Information: PowerShell module SqlServer not found, trying to use older SQLPS module. ImportedPowerShellModule = Importing PowerShell module '{0}' with version '{1}' from path '{2}'. @@ -144,5 +154,4 @@ ConvertFrom-StringData @' # SQLServerNetwork UnableToUseBothDynamicAndStaticPort = Unable to set both TCP dynamic port and TCP static port. Only one can be set. - '@ diff --git a/sv-SE/SqlServerDscHelper.strings.psd1 b/Modules/DscResource.Common/sv-SE/DscResource.Common.strings.psd1 similarity index 99% rename from sv-SE/SqlServerDscHelper.strings.psd1 rename to Modules/DscResource.Common/sv-SE/DscResource.Common.strings.psd1 index 905d49201..0d2ec4462 100644 --- a/sv-SE/SqlServerDscHelper.strings.psd1 +++ b/Modules/DscResource.Common/sv-SE/DscResource.Common.strings.psd1 @@ -13,7 +13,7 @@ ConvertFrom-StringData @' PropertiesDoesNotMatch = Hittade en matris för egenskapen {0} för nuvarande värden, men denna matris matchar inte önskat läge. Detaljer för ändringarna finns nedan. PropertyThatDoesNotMatch = {0} - {1} ValueOfTypeDoesNotMatch = {0} värde för egenskapen {1} matchar inte. Nuvarande läge är '{2}' och önskat läge är '{3}'. - UnableToCompareProperty = Inte möjligt att jämföra egenskapen {0} som typen {1}. {1} hanteras inte av Test-SQLDscParameterState cmdlet. + UnableToCompareProperty = Inte möjligt att jämföra egenskapen {0} som typen {1}. {1} hanteras inte av Test-DscParameterState cmdlet. PreferredModuleFound = Föredragen modul SqlServer funnen. PreferredModuleNotFound = Information: PowerShell modul SqlServer ej funnen, försöker att använda äldre SQLPS modul. ImportedPowerShellModule = Importerade PowerShell modul '{0}' med version '{1}' från mapp '{2}'. diff --git a/DSCResources/CommonResourceHelper.psm1 b/Modules/DscResource.LocalizationHelper/DscResource.LocalizationHelper.psm1 similarity index 89% rename from DSCResources/CommonResourceHelper.psm1 rename to Modules/DscResource.LocalizationHelper/DscResource.LocalizationHelper.psm1 index bf18d83b8..32a3b847e 100644 --- a/DSCResources/CommonResourceHelper.psm1 +++ b/Modules/DscResource.LocalizationHelper/DscResource.LocalizationHelper.psm1 @@ -1,3 +1,67 @@ +<# + .SYNOPSIS + Retrieves the localized string data based on the machine's culture. + Falls back to en-US strings if the machine's culture is not supported. + + .PARAMETER ResourceName + The name of the resource as it appears before '.strings.psd1' of the localized string file. + For example: + For WindowsOptionalFeature: MSFT_WindowsOptionalFeature + For Service: MSFT_ServiceResource + For Registry: MSFT_RegistryResource + For Helper: SqlServerDscHelper + + .PARAMETER ScriptRoot + Optional. The root path where to expect to find the culture folder. This is only needed + for localization in helper modules. This should not normally be used for resources. + + .NOTES + To be able to use localization in the helper function, this function must + be first in the file, before Get-LocalizedData is used by itself to load + localized data for this helper module (see directly after this function). +#> +function Get-LocalizedData +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ResourceName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ScriptRoot + ) + + if (-not $ScriptRoot) + { + $dscResourcesFolder = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'DSCResources' + $resourceDirectory = Join-Path -Path $dscResourcesFolder -ChildPath $ResourceName + } + else + { + $resourceDirectory = $ScriptRoot + } + + $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture + + if (-not (Test-Path -Path $localizedStringFileLocation)) + { + # Fallback to en-US + $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' + } + + Import-LocalizedData ` + -BindingVariable 'localizedData' ` + -FileName "$ResourceName.strings.psd1" ` + -BaseDirectory $localizedStringFileLocation + + return $localizedData +} + <# .SYNOPSIS Creates and throws an invalid argument exception. @@ -193,73 +257,10 @@ function New-InvalidResultException throw $errorRecordToThrow } -<# - .SYNOPSIS - Retrieves the localized string data based on the machine's culture. - Falls back to en-US strings if the machine's culture is not supported. - - .PARAMETER ResourceName - The name of the resource as it appears before '.strings.psd1' of the localized string file. - For example: - For WindowsOptionalFeature: MSFT_WindowsOptionalFeature - For Service: MSFT_ServiceResource - For Registry: MSFT_RegistryResource - For Helper: SqlServerDscHelper - - .PARAMETER ScriptRoot - Optional. The root path where to expect to find the culture folder. This is only needed - for localization in helper modules. This should not normally be used for resources. -#> -function Get-LocalizedData -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ResourceName, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ScriptRoot - ) - - if ( -not $ScriptRoot ) - { - $resourceDirectory = Join-Path -Path $PSScriptRoot -ChildPath $ResourceName - $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture - } - else - { - $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath $PSUICulture - } - - if (-not (Test-Path -Path $localizedStringFileLocation)) - { - # Fallback to en-US - if ( -not $ScriptRoot ) - { - $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' - } - else - { - $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath 'en-US' - } - } - - Import-LocalizedData ` - -BindingVariable 'localizedData' ` - -FileName "$ResourceName.strings.psd1" ` - -BaseDirectory $localizedStringFileLocation - - return $localizedData -} - Export-ModuleMember -Function @( 'New-InvalidArgumentException', 'New-InvalidOperationException', 'New-ObjectNotFoundException', - 'New-InvalidResultException', - 'Get-LocalizedData' ) + 'New-InvalidResultException' + 'Get-LocalizedData' +) diff --git a/README.md b/README.md index 54f363864..73c7a208e 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ A full list of changes in each version can be found in the [change log](CHANGELO database roles. * [**SqlRS**](#sqlrs) configures SQL Server Reporting. Services to use a database engine in another instance. +* [**SqlRSSetup**](#sqlrssetup) Installs the standalone + [Microsoft SQL Server Reporting Services](https://docs.microsoft.com/en-us/sql/reporting-services/create-deploy-and-manage-mobile-and-paginated-reports). * [**SqlScript**](#sqlscript) resource to extend DSC Get/Set/Test functionality to T-SQL. * [**SqlScriptQuery**](#sqlscriptquery) resource to extend DSC Get/Set/Test @@ -811,6 +813,106 @@ already exist. This is caused when trying to add another URL using the same protocol. For example when trying to add 'http://+:443' when 'http://+:80' already exist. +### SqlRSSetup + +Installs the standalone [Microsoft SQL Server Reporting Services](https://docs.microsoft.com/en-us/sql/reporting-services/create-deploy-and-manage-mobile-and-paginated-reports). + +If both `SourceCredential` and `PsDscRunAsCredential` is used then the +credentials in `SourceCredential` will only be used to copy the +installation media locally, and then the credentials in `PsDscRunAsCredential` +will be used during installation. If `PsDscRunAsCredential` is not +used, then the installation will run as SYSTEM. + +>To install Microsoft SQL Server Reporting Services 2016 (or older), +>please use the resource SqlSetup. + +#### Requirements + +* Target machine must be running Windows Server 2012 or later. +* If `PsDscRunAsCredential` common parameter is used to run the resource, + the specified credential must have permissions to connect to the location + where the Microsoft SQL Server Reporting Services media is placed. +* The parameter IAcceptLicenseTerms must be set to 'Yes'. +* The parameter InstanceName can only be set to 'SSRS' since there is + no way to change the instance name. +* When using action 'Uninstall', the same version of the executable as the version + of the installed product must be used. If not, sometimes the uninstall + is successful (because the executable returns exit code 0) but the + Microsoft SQL Server Reporting Services instance was not actually removed. + +>NOTE: When using the action 'Uninstall' and the target node to begin with +>requires a restart, on the first run the Microsoft SQL Server Reporting +>Services instance will not be uninstalled, but instead exit with code +>3010 and the node will be, by default, restarted. On the second run after +>restart, the Microsoft SQL Server Reporting Services instance will be +>uninstalled. If the parameter SuppressRestart is used, then the node must +>be restarted manually before the Microsoft SQL Server Reporting Services +>instance will be successfully uninstalled. +> +>The Microsoft SQL Server Reporting Services log will indicate that a +>restart is required by outputting; "*No action was taken as a system +>reboot is required (0x8007015E)*". The log is default located in the +>SSRS folder in `%TEMP%`, e.g. `C:\Users\\AppData\Local\Temp\SSRS`. + +#### Parameters + +* **`[String]` InstanceName** _(Key)_: Name of the Microsoft SQL Server + Reporting Service instance to installed. This can only be set to 'SSRS'. + { 'SSRS' } +* **`[String]` IAcceptLicenseTerms** _(Required)_: Accept licens terms. + This must be set to 'Yes'. { 'Yes' } +* **`[String]` SourcePath** _(Required)_: The path to the installation media + file to be used for installation, e.g an UNC path to a shared resource. + Environment variables can be used in the path. +* **`[String]` Action** _(Write)_: The action to be performed. Default + value is 'Install' which performs either install or upgrade. + { *Install* | Uninstall } +* **`[PSCredential]` SourceCredential** _(Write)_: Credentials used to + access the path set in the parameter 'SourcePath'. +* **`[Boolean]` SuppressRestart** _(Write)_: Suppresses any attempts to + restart. +* **`[String]` ProductKey** _(Write)_: Sets the custom license key, e.g. + '12345-12345-12345-12345-12345'. +* **`[Boolean]` ForceRestart** _(Write)_: Forces a restart after installation + is finished. +* **`[Boolean]` EditionUpgrade** _(Write)_: Upgrades the edition of the + installed product. Requires that either the ProductKey or the Edition + parameter is also assigned. By default no edition upgrade is performed. +* **`[Boolean]` VersionUpgrade** _(Write)_: Upgrades installed product + version, if the major product version of the source executable is higher + than the major current version. Requires that either the ProductKey or + the Edition parameter is also assigned. Default is $false. +* **`[String]` Edition** _(Write)_: Sets the custom free edition. + { 'Development' | 'Evaluation' | 'ExpressAdvanced' } +* **`[String]` LogPath** _(Write)_: Specifies the setup log file location, + e.g. 'log.txt'. By default, log files are created under `%TEMP%`. +* **`[String]` InstallFolder** _(Write)_: Sets the install folder, e.g. + 'C:\Program Files\SSRS'. Default value is 'C:\Program Files\Microsoft + SQL Server Reporting Services'. +* **`[UInt32]` SetupProcessTimeout** _(Write)_: The timeout, in seconds, to wait + for the setup process to finish. Default value is 7200 seconds (2 hours). If + the setup process does not finish before this time, and error will be thrown. + +#### Read-Only Properties from Get-TargetResource + +* **`[String]` ErrorDumpDirectory** _(Read)_: Returns the path to error + dump log files. +* **`[String]` CurrentVersion** _(Read)_: Returns the current version + of the installed Microsoft SQL Server Reporting Service instance. +* **`[String]` ServiceName** _(Read)_: Returns the current name + of the Microsoft SQL Server Reporting Service instance Windows service. + +#### Examples + +* [Install Reporting Services](Examples/Resources/SqlRSSetup/1-InstallReportingServices.ps1) +* [Uninstall Reporting Services](Examples/Resources/SqlRSSetup/2-UninstallReportingServices.ps1) + +#### Known issues + +* [SqlRSSetup: Will always make an edition upgrade](https://github.com/PowerShell/SqlServerDsc/issues/1311) + +All issues are not listed here, see [here for all open issues](https://github.com/PowerShell/SqlServerDsc/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+SqlRSSetup). + ### SqlScript Provides the means to run a user generated T-SQL script on the SQL Server instance. diff --git a/Tests/Integration/MSFT_SqlAgentOperator.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlAgentOperator.Integration.Tests.ps1 index a742fdee2..d417f72d5 100644 --- a/Tests/Integration/MSFT_SqlAgentOperator.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlAgentOperator.Integration.Tests.ps1 @@ -84,7 +84,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -132,7 +132,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlAlwaysOnService.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlAlwaysOnService.Integration.Tests.ps1 index c100fffbe..53a727398 100644 --- a/Tests/Integration/MSFT_SqlAlwaysOnService.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlAlwaysOnService.Integration.Tests.ps1 @@ -121,7 +121,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -167,7 +167,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } diff --git a/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.Integration.Tests.ps1 index a49b01f75..26a8d617f 100644 --- a/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.Integration.Tests.ps1 @@ -79,11 +79,11 @@ try } $resourceCurrentState.Type | Should -Be 'Data' - ( Join-Path -Path $resourceCurrentState.Path -ChildPath '' ) | Should -Be ( Join-Path -Path $ConfigurationData.AllNodes.DataFilePath -ChildPath '' ) + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.DataFilePath } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -126,11 +126,11 @@ try } $resourceCurrentState.Type | Should -Be 'Log' - ( Join-Path -Path $resourceCurrentState.Path -ChildPath '' ) | Should -Be ( Join-Path -Path $ConfigurationData.AllNodes.LogFilePath -ChildPath '' ) + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.LogFilePath } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -173,11 +173,12 @@ try } $resourceCurrentState.Type | Should -Be 'Backup' - ( Join-Path -Path $resourceCurrentState.Path -ChildPath '' ) | Should -Be ( Join-Path -Path $ConfigurationData.AllNodes.BackupFilePath -ChildPath '' ) + # Ending backslash is removed because of regression test for issue #1307. + $resourceCurrentState.Path | Should -Be $ConfigurationData.AllNodes.BackupFilePath.TrimEnd('\') } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.config.ps1 b/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.config.ps1 index 92c84c78e..acb624eec 100644 --- a/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.config.ps1 +++ b/Tests/Integration/MSFT_SqlDatabaseDefaultLocation.config.ps1 @@ -27,6 +27,8 @@ else DataFilePath = 'C:\SQLData\' LogFilePath = 'C:\SQLLog\' + + # Ending backslash is regression test for issue #1307. BackupFilePath = 'C:\Backups\' CertificateFile = $env:DscPublicCertificatePath diff --git a/Tests/Integration/MSFT_SqlRS.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlRS.Integration.Tests.ps1 index c072f9b99..492fdb216 100644 --- a/Tests/Integration/MSFT_SqlRS.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlRS.Integration.Tests.ps1 @@ -1,5 +1,5 @@ # This is used to make sure the integration test run in the correct order. -[Microsoft.DscResourceKit.IntegrationTest(OrderNumber = 2)] +[Microsoft.DscResourceKit.IntegrationTest(OrderNumber = 3)] param() Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') @@ -113,7 +113,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } It 'Should be able to access the ReportServer site without any error' { @@ -203,7 +203,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } <# @@ -261,7 +261,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } It 'Should be able to access the ReportServer site without any error' { diff --git a/Tests/Integration/MSFT_SqlRSSetup.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlRSSetup.Integration.Tests.ps1 new file mode 100644 index 000000000..9e02134a0 --- /dev/null +++ b/Tests/Integration/MSFT_SqlRSSetup.Integration.Tests.ps1 @@ -0,0 +1,161 @@ +# This is used to make sure the integration test run in the correct order. +[Microsoft.DscResourceKit.IntegrationTest(OrderNumber = 2)] +param() + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +# Run only for SQL 2017 integration testing. +if (Test-SkipContinuousIntegrationTask -Type 'Integration' -Category @('Integration_SQL2017')) +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceFriendlyName = 'SqlRSSetup' +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" + +#region HEADER +# Integration Test Template Version: 1.3.2 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -TestType Integration +#endregion + +# Using try/finally to always cleanup. +try +{ + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile + + # Download Microsoft SQL Server Reporting Services (October 2017) executable + if (-not (Test-Path -Path $ConfigurationData.AllNodes.SourcePath)) + { + # By switching to 'SilentlyContinue' should theoretically increase the download speed. + $previousProgressPreference = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' + + $script:mockSourceMediaDisplayName = 'Microsoft SQL Server Reporting Services (October 2017)' + $script:mockSourceMediaUrl = 'https://download.microsoft.com/download/E/6/4/E6477A2A-9B58-40F7-8AD6-62BB8491EA78/SQLServerReportingServices.exe' + + Write-Info -Message ('Start downloading the {1} executable at {0}.' -f (Get-Date -Format 'yyyy-MM-dd hh:mm:ss'), $script:mockSourceMediaDisplayName) -Verbose + + Invoke-WebRequest -Uri $script:mockSourceMediaUrl -OutFile $ConfigurationData.AllNodes.SourcePath + + Write-Info -Message ('{1} executable file has SHA1 hash ''{0}''.' -f (Get-FileHash -Path $ConfigurationData.AllNodes.SourcePath -Algorithm 'SHA1').Hash, $script:mockSourceMediaDisplayName) -Verbose + + $ProgressPreference = $previousProgressPreference + + # Double check that the Microsoft SQL Server Reporting Services (October 2017) was downloaded. + if (-not (Test-Path -Path $ConfigurationData.AllNodes.SourcePath)) + { + Write-Warning -Message ('{0} executable could not be downloaded, can not run the integration test.' -f $script:mockSourceMediaDisplayName) + return + } + else + { + Write-Info -Message ('Finished downloading the {1} executable at {0}.' -f (Get-Date -Format 'yyyy-MM-dd hh:mm:ss'), $script:mockSourceMediaDisplayName) -Verbose + } + } + else + { + Write-Info -Message ('{0} executable is already downloaded' -f $script:mockSourceMediaDisplayName) -Verbose + } + + Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $configurationName = "$($script:dscResourceName)_InstallReportingServicesAsUser_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.InstanceName | Should -Be $ConfigurationData.AllNodes.InstanceName + $resourceCurrentState.InstallFolder | Should -Be 'C:\Program Files\Microsoft SQL Server Reporting Services' + $resourceCurrentState.ServiceName | Should -Be 'SQLServerReportingServices' + $resourceCurrentState.ErrorDumpDirectory | Should -Be 'C:\Program Files\Microsoft SQL Server Reporting Services\SSRS\LogFiles' + $resourceCurrentState.CurrentVersion | Should -BeGreaterThan ([System.Version] '14.0.0.0') + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_StopReportingServicesInstance_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + } + } +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/Tests/Integration/MSFT_SqlRSSetup.config.ps1 b/Tests/Integration/MSFT_SqlRSSetup.config.ps1 new file mode 100644 index 000000000..c9db6c923 --- /dev/null +++ b/Tests/Integration/MSFT_SqlRSSetup.config.ps1 @@ -0,0 +1,90 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, + for real testing scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + InstanceName = 'SSRS' + IAcceptLicenseTerms = 'Yes' + SourcePath = Join-Path -Path $env:TEMP -ChildPath 'SQLServerReportingServices.exe' + Edition = 'Development' + + UserName = "$env:COMPUTERNAME\SqlInstall" + Password = 'P@ssw0rd1' + + CertificateFile = $env:DscPublicCertificatePath + } + ) + } +} + +<# + .SYNOPSIS + Installs a Microsoft SQL Server Reporting Services instance. + + .NOTES + When this test was written the build worker already contained a + Microsoft SQL Server Reporting Services instance. + If it exist, it will be upgraded. + + Uninstall is not tested, because when upgrading the existing Microsoft + SQL Server 2017 Reporting Services instance it requires a restart which + prevents uninstall until the node is rebooted. +#> +Configuration MSFT_SqlRSSetup_InstallReportingServicesAsUser_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlRSSetup 'Integration_Test' + { + InstanceName = $Node.InstanceName + IAcceptLicenseTerms = $Node.IAcceptLicenseTerms + SourcePath = $Node.SourcePath + Edition = $Node.Edition + + # The build worker contains already an instance, make sure to upgrade it. + VersionUpgrade = $true + + # Suppressing restart because the build worker are not allowed to be restarted. + SuppressRestart = $true + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Stopping the Microsoft SQL Server Reporting Services instance to + save memory on the build worker. +#> +Configuration MSFT_SqlRSSetup_StopReportingServicesInstance_Config +{ + Import-DscResource -ModuleName 'PSDscResources' + + node $AllNodes.NodeName + { + Service 'StopReportingServicesInstance' + { + Name = 'SQLServerReportingServices' + State = 'Stopped' + } + } +} diff --git a/Tests/Integration/MSFT_SqlScript.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlScript.Integration.Tests.ps1 index ddaec72df..253986735 100644 --- a/Tests/Integration/MSFT_SqlScript.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlScript.Integration.Tests.ps1 @@ -159,7 +159,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -208,7 +208,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlScriptQuery.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlScriptQuery.Integration.Tests.ps1 index 8c4376419..9e188b375 100644 --- a/Tests/Integration/MSFT_SqlScriptQuery.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlScriptQuery.Integration.Tests.ps1 @@ -127,7 +127,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -176,7 +176,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlServerDatabaseMail.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServerDatabaseMail.Integration.Tests.ps1 index a10bfafc0..781aa30a5 100644 --- a/Tests/Integration/MSFT_SqlServerDatabaseMail.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlServerDatabaseMail.Integration.Tests.ps1 @@ -91,7 +91,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -146,7 +146,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlServerEndPoint.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServerEndPoint.Integration.Tests.ps1 index c15753605..f6bfdc140 100644 --- a/Tests/Integration/MSFT_SqlServerEndPoint.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlServerEndPoint.Integration.Tests.ps1 @@ -86,7 +86,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -132,7 +132,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlServerLogin.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServerLogin.Integration.Tests.ps1 index d423666b6..d77714b44 100644 --- a/Tests/Integration/MSFT_SqlServerLogin.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlServerLogin.Integration.Tests.ps1 @@ -117,7 +117,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -166,7 +166,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -215,7 +215,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -264,7 +264,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -313,7 +313,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -361,7 +361,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlServerNetwork.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServerNetwork.Integration.Tests.ps1 index 54da4494c..5422de148 100644 --- a/Tests/Integration/MSFT_SqlServerNetwork.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlServerNetwork.Integration.Tests.ps1 @@ -89,7 +89,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -138,7 +138,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlServerRole.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServerRole.Integration.Tests.ps1 index a94d83f05..724d3ae85 100644 --- a/Tests/Integration/MSFT_SqlServerRole.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlServerRole.Integration.Tests.ps1 @@ -87,7 +87,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -138,7 +138,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -192,7 +192,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -246,7 +246,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -305,7 +305,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -359,7 +359,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -410,7 +410,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlServerSecureConnection.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServerSecureConnection.Integration.Tests.ps1 index 760d3369e..b2524c9be 100644 --- a/Tests/Integration/MSFT_SqlServerSecureConnection.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlServerSecureConnection.Integration.Tests.ps1 @@ -100,7 +100,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -147,7 +147,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlServiceAccount.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServiceAccount.Integration.Tests.ps1 index c3157e203..b04b5b952 100644 --- a/Tests/Integration/MSFT_SqlServiceAccount.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlServiceAccount.Integration.Tests.ps1 @@ -119,7 +119,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -166,7 +166,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -213,7 +213,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -260,7 +260,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -334,7 +334,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -381,7 +381,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -428,7 +428,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -475,7 +475,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } } diff --git a/Tests/Integration/MSFT_SqlSetup.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlSetup.Integration.Tests.ps1 index eaf77e99e..155fa718b 100644 --- a/Tests/Integration/MSFT_SqlSetup.Integration.Tests.ps1 +++ b/Tests/Integration/MSFT_SqlSetup.Integration.Tests.ps1 @@ -285,7 +285,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -424,7 +424,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } @@ -556,7 +556,7 @@ try } It 'Should return $true when Test-DscConfiguration is run' { - Test-DscConfiguration -Verbose | Should -Be $true + Test-DscConfiguration -Verbose | Should -Be 'True' } } diff --git a/Tests/Integration/README.md b/Tests/Integration/README.md index 77b3c6e63..eaa53f575 100644 --- a/Tests/Integration/README.md +++ b/Tests/Integration/README.md @@ -46,6 +46,7 @@ be used by other integration tests. > Note: User account names was kept to a maximum of 15 characters. + User | Password | Permission | Description --- | --- | --- | --- .\SqlInstall | P@ssw0rd1 | Local Windows administrator. Administrator of Database Engine instance DSCSQLTEST\*. | Runs Setup for the default instance. @@ -55,11 +56,54 @@ User | Password | Permission | Description .\svc-SqlSecondary | yig-C^Equ3 | Local user. | Used by other tests, but created here. .\svc-SqlAgentSec | yig-C^Equ3 | Local user. | Used by other tests. sa | P@ssw0rd1 | Administrator of the Database Engine instances DSCSQLTEST. | + *\* This is due to that the integration tests runs the resource SqlAlwaysOnService with this user and that means that this user must have permission to access the properties `IsClustered` and `IsHadrEnable`.* +## SqlRSSetup + +**Run order:** 2 + +**Depends on:** SqlSetup (for the local installation account) + +The integration tests will install (or upgrade) a Microsoft SQL Server +2017 Reporting Services instance and leave it on the AppVeyor build worker +for other integration tests to use. + +>**NOTE:** Uninstall is not tested, because when upgrading the existing +>Microsoft SQL Server Reporting Services instance it requires a restart, +>that prevents uninstall until the node is restarted. AppVeyor build +>workers are not allowed to be restarted during testing phase. + +Instance | State +--- | --- +SSRS | Stopped + +>**Note:** The Reporting Services instance is not configured after it is +>installed or upgraded, but if there are already an instance of Reporting +>Services installed on the build worker, it could have been configured. +>Other integration tests need to take that into consideration. + +### Properties for the instance SSRS + +- **InstanceName:** SSRS +- **CurrentVersion:** ^14.0.6981.38291 (depends on the version downloaded) +- **ErrorDumpDirectory:** C:\Program Files\Microsoft SQL Server Reporting Services\SSRS\LogFiles +- **LogPath:** C:\Users\appveyor\AppData\Local\Temp\SSRS +- **InstallFolder:** C:\Program Files\Microsoft SQL Server Reporting Services +- **ServiceName:** SQLServerReportingServices +- **Edition:** Developer + +### Users + + +User | Password | Description +--- | --- | --- | --- +.\SqlInstall | P@ssw0rd1 | The Reporting Services instance is installed using this account. + + ## SqlAlwaysOnService **Run order:** 2 @@ -71,41 +115,19 @@ an IP address of '192.168.40.10'. To be able to activate the AlwaysOn service th tests creates an Active Directory Detached Cluster with an IP address of '192.168.40.11' and the cluster will ignore any other static IP addresses. ->**Note:** During the tests the gateway of the loopback adatper named 'ClusterNetwork' + +>**Note:** During the tests the gateway of the loopback adapter named 'ClusterNetwork' >will be set to '192.168.40.254', because it is a requirement to create the cluster, >but the gateway will be removed in the last clean up test. Gateway is removed so >that there will be no conflict with the default gateway. ->*Note:** The Active Directory Detached Cluster is not fully functioning in the +>**Note:** The Active Directory Detached Cluster is not fully functioning in the >sense that it cannot start the Name resource in the 'Cluster Group', but it >starts enough to be able to run integration tests for AlwaysOn service.s + The tests will leave the AlwaysOn service disabled. -## SqlRS - -**Run order:** 2 - -**Depends on:** SqlSetup - -The integration tests will install the following instances and leave it on the -AppVeyor build worker for other integration tests to use. - -Instance | Feature | Description ---- | --- | --- -DSCRS2016 | RS | The Reporting Services is initialized, and in a working state. - ->**Note:** The Reporting Services service is stopped to save memory on the build ->worker. - -### Properties for the instance - -- **Collation:** Finnish\_Swedish\_CI\_AS -- **InstallSharedDir:** C:\Program Files\Microsoft SQL Server -- **InstallSharedWOWDir:** C:\Program Files (x86)\Microsoft SQL Server -- **DatabaseServerName:** `$env:COMPUTERNAME` -- **DatabaseInstanceName:** DSCSQLTEST - ## SqlDatabaseDefaultLocation **Run order:** 2 @@ -153,6 +175,30 @@ DscUser4 | SQL | P@ssw0rd1 | *None* > **Note:** Login DscUser3 was create disabled and was used to test removal of > a login. +## SqlRS + +**Run order:** 3 + +**Depends on:** SqlSetup, SqlRSSetup + +The integration tests will install the following instances and leave it on the +AppVeyor build worker for other integration tests to use. + +Instance | Feature | Description +--- | --- | --- +DSCRS2016 | RS | The Reporting Services is initialized, and in a working state. + +>**Note:** The Reporting Services service is stopped to save memory on the build +>worker. + +### Properties for the instance + +- **Collation:** Finnish\_Swedish\_CI\_AS +- **InstallSharedDir:** C:\Program Files\Microsoft SQL Server +- **InstallSharedWOWDir:** C:\Program Files (x86)\Microsoft SQL Server +- **DatabaseServerName:** `$env:COMPUTERNAME` +- **DatabaseInstanceName:** DSCSQLTEST + ## SqlServerRole **Run order:** 3 diff --git a/Tests/Unit/CommonResourceHelper.Tests.ps1 b/Tests/Unit/CommonResourceHelper.Tests.ps1 deleted file mode 100644 index 3206ee4cd..000000000 --- a/Tests/Unit/CommonResourceHelper.Tests.ps1 +++ /dev/null @@ -1,210 +0,0 @@ -<# - .SYNOPSIS - Automated unit test for helper functions in module CommonResourceHelper. - - .NOTES - To run this script locally, please make sure to first run the bootstrap - script. Read more at - https://github.com/PowerShell/SqlServerDsc/blob/dev/CONTRIBUTING.md#bootstrap-script-assert-testenvironment -#> - -Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') - -if (Test-SkipContinuousIntegrationTask -Type 'Unit') -{ - return -} - -$script:helperModuleName = 'CommonResourceHelper' - -Describe "$script:helperModuleName Unit Tests" { - BeforeAll { - # Import the CommonResourceHelper module to test - $dscResourcesFolderFilePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) ` - -ChildPath 'DscResources' - - Import-Module -Name (Join-Path -Path $dscResourcesFolderFilePath ` - -ChildPath "$script:helperModuleName.psm1") -Force - } - - InModuleScope $script:helperModuleName { - Describe 'Get-LocalizedData' { - $mockTestPath = { - return $mockTestPathReturnValue - } - - $mockImportLocalizedData = { - $BaseDirectory | Should -Be $mockExpectedLanguagePath - } - - BeforeEach { - Mock -CommandName Test-Path -MockWith $mockTestPath -Verifiable - Mock -CommandName Import-LocalizedData -MockWith $mockImportLocalizedData -Verifiable - } - - Context 'When loading localized data for Swedish' { - $mockExpectedLanguagePath = 'sv-SE' - $mockTestPathReturnValue = $true - - It 'Should call Import-LocalizedData with sv-SE language' { - Mock -CommandName Join-Path -MockWith { - return 'sv-SE' - } -Verifiable - - { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw - - Assert-MockCalled -CommandName Join-Path -Exactly -Times 2 -Scope It - Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It - } - - $mockExpectedLanguagePath = 'en-US' - $mockTestPathReturnValue = $false - - It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' { - Mock -CommandName Join-Path -MockWith { - return $ChildPath - } -Verifiable - - { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw - - Assert-MockCalled -CommandName Join-Path -Exactly -Times 3 -Scope It - Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It - } - - Context 'When $ScriptRoot is set to a path' { - $mockExpectedLanguagePath = 'sv-SE' - $mockTestPathReturnValue = $true - - It 'Should call Import-LocalizedData with sv-SE language' { - Mock -CommandName Join-Path -MockWith { - return 'sv-SE' - } -Verifiable - - { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should -Not -Throw - - Assert-MockCalled -CommandName Join-Path -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It - } - - $mockExpectedLanguagePath = 'en-US' - $mockTestPathReturnValue = $false - - It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' { - Mock -CommandName Join-Path -MockWith { - return $ChildPath - } -Verifiable - - { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should -Not -Throw - - Assert-MockCalled -CommandName Join-Path -Exactly -Times 2 -Scope It - Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It - } - } - } - - Context 'When loading localized data for English' { - Mock -CommandName Join-Path -MockWith { - return 'en-US' - } -Verifiable - - $mockExpectedLanguagePath = 'en-US' - $mockTestPathReturnValue = $true - - It 'Should call Import-LocalizedData with en-US language' { - { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw - } - } - - Assert-VerifiableMock - } - - Describe 'New-InvalidResultException' { - Context 'When calling with Message parameter only' { - It 'Should throw the correct error' { - $mockErrorMessage = 'Mocked error' - - { New-InvalidResultException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage - } - } - - Context 'When calling with both the Message and ErrorRecord parameter' { - It 'Should throw the correct error' { - $mockErrorMessage = 'Mocked error' - $mockExceptionErrorMessage = 'Mocked exception error message' - - $mockException = New-Object -TypeName System.Exception -ArgumentList $mockExceptionErrorMessage - $mockErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $mockException, $null, 'InvalidResult', $null - - { New-InvalidResultException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) - } - } - - Assert-VerifiableMock - } - - Describe 'New-ObjectNotFoundException' { - Context 'When calling with Message parameter only' { - It 'Should throw the correct error' { - $mockErrorMessage = 'Mocked error' - - { New-ObjectNotFoundException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage - } - } - - Context 'When calling with both the Message and ErrorRecord parameter' { - It 'Should throw the correct error' { - $mockErrorMessage = 'Mocked error' - $mockExceptionErrorMessage = 'Mocked exception error message' - - $mockException = New-Object -TypeName System.Exception -ArgumentList $mockExceptionErrorMessage - $mockErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $mockException, $null, 'InvalidResult', $null - - { New-ObjectNotFoundException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) - } - } - - Assert-VerifiableMock - } - - Describe 'New-InvalidOperationException' { - Context 'When calling with Message parameter only' { - It 'Should throw the correct error' { - $mockErrorMessage = 'Mocked error' - - { New-InvalidOperationException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage - } - } - - Context 'When calling with both the Message and ErrorRecord parameter' { - It 'Should throw the correct error' { - $mockErrorMessage = 'Mocked error' - $mockExceptionErrorMessage = 'Mocked exception error message' - - $mockException = New-Object -TypeName System.Exception -ArgumentList $mockExceptionErrorMessage - $mockErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $mockException, $null, 'InvalidResult', $null - - { New-InvalidOperationException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.InvalidOperationException: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) - } - } - - Assert-VerifiableMock - } - - Describe 'New-InvalidArgumentException' { - Context 'When calling with both the Message and ArgumentName parameter' { - It 'Should throw the correct error' { - $mockErrorMessage = 'Mocked error' - $mockArgumentName = 'MockArgument' - - { New-InvalidArgumentException -Message $mockErrorMessage -ArgumentName $mockArgumentName } | Should -Throw ('Parameter name: {0}' -f $mockArgumentName) - } - } - - Assert-VerifiableMock - } - } -} diff --git a/Tests/Unit/SqlServerDSCHelper.Tests.ps1 b/Tests/Unit/DscResource.Common.Tests.ps1 similarity index 64% rename from Tests/Unit/SqlServerDSCHelper.Tests.ps1 rename to Tests/Unit/DscResource.Common.Tests.ps1 index 451d7e6ff..6e175df60 100644 --- a/Tests/Unit/SqlServerDSCHelper.Tests.ps1 +++ b/Tests/Unit/DscResource.Common.Tests.ps1 @@ -1,6 +1,6 @@ <# .SYNOPSIS - Automated unit test for helper functions in module SqlServerDscHelper. + Automated unit test for helper functions in module DscResource.Common. .NOTES To run this script locally, please make sure to first run the bootstrap @@ -8,10 +8,6 @@ https://github.com/PowerShell/SqlServerDsc/blob/dev/CONTRIBUTING.md#bootstrap-script-assert-testenvironment #> -# To run these tests, we have to fake login credentials -[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] -param () - Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') if (Test-SkipContinuousIntegrationTask -Type 'Unit') @@ -19,1760 +15,2392 @@ if (Test-SkipContinuousIntegrationTask -Type 'Unit') return } -# Unit Test Template Version: 1.1.0 - -$script:helperModuleName = 'SqlServerDscHelper' - -[System.String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) -if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) -} +# Import the DscResource.Common module to test +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules\DscResource.Common' -Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -Import-Module (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent | Split-Path -Parent) -ChildPath "$script:helperModuleName.psm1") -Scope Global -Force +Import-Module -Name (Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common.psm1') -Force # Loading mocked classes -Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) - -Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs') -Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Global -Force - -# Begin Testing -InModuleScope $script:helperModuleName { - $mockNewObject_MicrosoftAnalysisServicesServer = { - return New-Object -TypeName Object | - Add-Member -MemberType ScriptMethod -Name Connect -Value { - param( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $dataSource - ) - - if ($dataSource -ne $mockExpectedDataSource) - { - throw ("Datasource was expected to be '{0}', but was '{1}'." -f $mockExpectedDataSource,$dataSource) - } - - if ($mockThrowInvalidOperation) - { - throw 'Unable to connect.' - } - } -PassThru -Force - } +Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') +Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs') - $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter = { - $TypeName -eq 'Microsoft.AnalysisServices.Server' - } +# Importing SQLPS stubs +Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global - $mockNewObject_MicrosoftDatabaseEngine = { - <# - $ArgumentList[0] will contain the ServiceInstance when calling mock New-Object. - But since the mock New-Object will also be called without arguments, we first - have to evaluate if $ArgumentList contains values. - #> - if( $ArgumentList.Count -gt 0) - { - $serverInstance = $ArgumentList[0] - } +InModuleScope 'DscResource.Common' { + Describe 'DscResource.Common\Test-DscParameterState' -Tag 'TestDscParameterState' { + Context -Name 'When passing values' -Fixture { + It 'Should return true for two identical tables' { + $mockDesiredValues = @{ Example = 'test' } - return New-Object -TypeName Object | - Add-Member -MemberType ScriptProperty -Name Status -Value { - if ($mockExpectedDatabaseEngineInstance -eq 'MSSQLSERVER') - { - $mockExpectedServiceInstance = $mockExpectedDatabaseEngineServer - } - else - { - $mockExpectedServiceInstance = "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" + $testParameters = @{ + CurrentValues = $mockDesiredValues + DesiredValues = $mockDesiredValues } - if ( $this.ConnectionContext.ServerInstance -eq $mockExpectedServiceInstance ) - { - return 'Online' - } - else - { - return $null - } - } -PassThru | - Add-Member -MemberType NoteProperty -Name ConnectionContext -Value ( - New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name ServerInstance -Value $serverInstance -PassThru | - #Add-Member -MemberType ScriptProperty -Name LoginSecure -Value { [System.Boolean] $mockExpectedDatabaseEngineLoginSecure } -PassThru -Force | - Add-Member -MemberType NoteProperty -Name LoginSecure -Value $true -PassThru | - Add-Member -MemberType NoteProperty -Name Login -Value '' -PassThru | - Add-Member -MemberType NoteProperty -Name SecurePassword -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name ConnectAsUser -Value $false -PassThru | - Add-Member -MemberType NoteProperty -Name ConnectAsUserPassword -Value '' -PassThru | - Add-Member -MemberType NoteProperty -Name ConnectAsUserName -Value '' -PassThru | - Add-Member -MemberType ScriptMethod -Name Connect -Value { - if ($mockExpectedDatabaseEngineInstance -eq 'MSSQLSERVER') - { - $mockExpectedServiceInstance = $mockExpectedDatabaseEngineServer - } - else - { - $mockExpectedServiceInstance = "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - } + Test-DscParameterState @testParameters | Should -Be $true + } - if ($this.serverInstance -ne $mockExpectedServiceInstance) - { - throw ("Mock method Connect() was expecting ServerInstance to be '{0}', but was '{1}'." -f $mockExpectedServiceInstance, $this.serverInstance ) - } + It 'Should return false when a value is different for [System.String]' { + $mockCurrentValues = @{ Example = [System.String] 'something' } + $mockDesiredValues = @{ Example = [System.String] 'test' } - if ($mockThrowInvalidOperation) - { - throw 'Unable to connect.' - } - } -PassThru -Force - ) -PassThru -Force - } + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } - $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter = { - $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Server' - } + Test-DscParameterState @testParameters | Should -Be $false + } - $mockThrowLocalizedMessage = { - throw $Message - } + It 'Should return false when a value is different for [System.Int32]' { + $mockCurrentValues = @{ Example = [System.Int32] 1 } + $mockDesiredValues = @{ Example = [System.Int32] 2 } - $mockSqlMajorVersion = 13 - $mockInstanceName = 'TEST' - - $mockSetupCredentialUserName = 'TestUserName12345' - $mockSetupCredentialPassword = 'StrongOne7.' - $mockSetupCredentialSecurePassword = ConvertTo-SecureString -String $mockSetupCredentialPassword -AsPlainText -Force - $mockSetupCredential = New-Object -TypeName PSCredential -ArgumentList ($mockSetupCredentialUserName, $mockSetupCredentialSecurePassword) - - $mockLocalSystemAccountUserName = 'NT AUTHORITY\SYSTEM' - $mockLocalSystemAccountCredential = New-Object System.Management.Automation.PSCredential $mockLocalSystemAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) - $mockManagedServiceAccountUserName = 'CONTOSO\msa$' - $mockManagedServiceAccountCredential = New-Object System.Management.Automation.PSCredential $mockManagedServiceAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) - $mockDomainAccountUserName = 'CONTOSO\User1' - $mockLocalServiceAccountUserName = 'NT SERVICE\MyService' - $mockLocalServiceAccountCredential = New-Object System.Management.Automation.PSCredential $mockLocalServiceAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) - $mockDomainAccountCredential = New-Object System.Management.Automation.PSCredential $mockDomainAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) - $mockInnerException = New-Object System.Exception "This is a mock inner excpetion object" - $mockInnerException | Add-Member -Name 'Number' -Value 2 -MemberType NoteProperty - $mockException = New-Object System.Exception "This is a mock exception object", $mockInnerException - $mockException | Add-Member -Name 'Number' -Value 1 -MemberType NoteProperty - - - Describe 'Testing Restart-SqlService' { - Context 'Restart-SqlService standalone instance' { - BeforeEach { - Mock -CommandName Connect-SQL -MockWith { - return @{ - Name = 'MSSQLSERVER' - InstanceName = '' - ServiceName = 'MSSQLSERVER' - Status = $mockDynamicStatus - IsClustered = $false - } - } -Verifiable -ParameterFilter { $InstanceName -eq 'MSSQLSERVER' } + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } - Mock -CommandName Connect-SQL -MockWith { - return @{ - Name = 'NOCLUSTERCHECK' - InstanceName = 'NOCLUSTERCHECK' - ServiceName = 'NOCLUSTERCHECK' - Status = $mockDynamicStatus - IsClustered = $true - } - } -Verifiable -ParameterFilter { $InstanceName -eq 'NOCLUSTERCHECK' } + Test-DscParameterState @testParameters | Should -Be $false + } - Mock -CommandName Connect-SQL -MockWith { - return @{ - Name = 'NOCONNECT' - InstanceName = 'NOCONNECT' - ServiceName = 'NOCONNECT' - Status = $mockDynamicStatus - IsClustered = $true - } - } -Verifiable -ParameterFilter { $InstanceName -eq 'NOCONNECT' } + It 'Should return false when a value is different for [Int16]' { + $mockCurrentValues = @{ Example = [System.Int16] 1 } + $mockDesiredValues = @{ Example = [System.Int16] 2 } - Mock -CommandName Connect-SQL -MockWith { - return @{ - Name = 'NOAGENT' - InstanceName = 'NOAGENT' - ServiceName = 'NOAGENT' - Status = $mockDynamicStatus - } - } -Verifiable -ParameterFilter { $InstanceName -eq 'NOAGENT' } + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } - Mock -CommandName Connect-SQL -MockWith { - return @{ - Name = 'STOPPEDAGENT' - InstanceName = 'STOPPEDAGENT' - ServiceName = 'STOPPEDAGENT' - Status = $mockDynamicStatus - } - } -Verifiable -ParameterFilter { $InstanceName -eq 'STOPPEDAGENT' } + Test-DscParameterState @testParameters | Should -Be $false } - BeforeAll { - ## SQL instance with running SQL Agent Service - Mock -CommandName Get-Service -MockWith { - return @{ - Name = 'MSSQLSERVER' - DisplayName = 'Microsoft SQL Server (MSSQLSERVER)' - DependentServices = @( - @{ - Name = 'SQLSERVERAGENT' - DisplayName = 'SQL Server Agent (MSSQLSERVER)' - Status = 'Running' - DependentServices = @() - } - ) - } - } -Verifiable -ParameterFilter { $Name -eq 'MSSQLSERVER' } + It 'Should return false when a value is different for [UInt16]' { + $mockCurrentValues = @{ Example = [System.UInt16] 1 } + $mockDesiredValues = @{ Example = [System.UInt16] 2 } - ## SQL instance with no installed SQL Agent Service - Mock -CommandName Get-Service -MockWith { - return @{ - Name = 'MSSQL$NOAGENT' - DisplayName = 'Microsoft SQL Server (NOAGENT)' - DependentServices = @() - } - } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$NOAGENT' } + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } - ## SQL instance with no installed SQL Agent Service - Mock -CommandName Get-Service -MockWith { - return @{ - Name = 'MSSQL$NOCLUSTERCHECK' - DisplayName = 'Microsoft SQL Server (NOCLUSTERCHECK)' - DependentServices = @() - } - } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$NOCLUSTERCHECK' } + Test-DscParameterState @testParameters | Should -Be $false + } - ## SQL instance with no installed SQL Agent Service - Mock -CommandName Get-Service -MockWith { - return @{ - Name = 'MSSQL$NOCONNECT' - DisplayName = 'Microsoft SQL Server (NOCONNECT)' - DependentServices = @() - } - } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$NOCONNECT' } + It 'Should return false when a value is different for [Boolean]' { + $mockCurrentValues = @{ Example = [System.Boolean] $true } + $mockDesiredValues = @{ Example = [System.Boolean] $false } - ## SQL instance with stopped SQL Agent Service - Mock -CommandName Get-Service -MockWith { - return @{ - Name = 'MSSQL$STOPPEDAGENT' - DisplayName = 'Microsoft SQL Server (STOPPEDAGENT)' - DependentServices = @( - @{ - Name = 'SQLAGENT$STOPPEDAGENT' - DisplayName = 'SQL Server Agent (STOPPEDAGENT)' - Status = 'Stopped' - DependentServices = @() - } - ) - } - } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$STOPPEDAGENT' } + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } - Mock -CommandName Restart-Service -Verifiable - Mock -CommandName Start-Service -Verifiable + Test-DscParameterState @testParameters | Should -Be $false } - $mockDynamicStatus = 'Online' + It 'Should return false when a value is missing' { + $mockCurrentValues = @{ } + $mockDesiredValues = @{ Example = 'test' } - It 'Should restart SQL Service and running SQL Agent service' { - { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'MSSQLSERVER' } | Should -Not -Throw + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } - Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $false - } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 1 + Test-DscParameterState @testParameters | Should -Be $false } - It 'Should restart SQL Service, and not do cluster cluster check' { - Mock -CommandName Get-CimInstance + It 'Should return true when only a specified value matches, but other non-listed values do not' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } + $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } - { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'NOCLUSTERCHECK' -SkipClusterCheck } | Should -Not -Throw + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('Example') + } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 - Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 0 + Test-DscParameterState @testParameters | Should -Be $true } - It 'Should restart SQL Service, and not do cluster cluster check nor check online status' { - Mock -CommandName Get-CimInstance + It 'Should return false when only specified values do not match, but other non-listed values do ' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } + $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } - { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'NOCONNECT' -SkipClusterCheck -SkipWaitForOnline } | Should -Not -Throw + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('SecondExample') + } - Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Connect-SQL -Scope It -Exactly -Times 0 - Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 - Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 0 + Test-DscParameterState @testParameters | Should -Be $false } - It 'Should restart SQL Service and not try to restart missing SQL Agent service' { - { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'NOAGENT' } | Should -Not -Throw + It 'Should return false when an empty hash table is used in the current values' { + $mockCurrentValues = @{ } + $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } - Assert-MockCalled -CommandName Connect-SQL { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $false - } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + Test-DscParameterState @testParameters | Should -Be $false } - It 'Should restart SQL Service and not try to restart stopped SQL Agent service' { - { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'STOPPEDAGENT' } | Should -Not -Throw + It 'Should return true when evaluating a table against a CimInstance' { + $mockCurrentValues = @{ Handle = '0'; ProcessId = '1000' } - Assert-MockCalled -CommandName Connect-SQL { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $false - } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 - } + $mockWin32ProcessProperties = @{ + Handle = 0 + ProcessId = 1000 + } - Context 'When it fails to connect to the instance within the timeout period' { - BeforeEach { - Mock -CommandName Connect-SQL -MockWith { - return @{ - Name = 'MSSQLSERVER' - InstanceName = '' - ServiceName = 'MSSQLSERVER' - Status = $mockDynamicStatus - } - } -Verifiable -ParameterFilter { $InstanceName -eq 'MSSQLSERVER' } + $mockNewCimInstanceParameters = @{ + ClassName = 'Win32_Process' + Property = $mockWin32ProcessProperties + Key = 'Handle' + ClientOnly = $true } - $mockDynamicStatus = 'Offline' + $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters - It 'Should throw the correct error message' { - $errorMessage = $localizedData.FailedToConnectToInstanceTimeout -f $env:ComputerName, 'MSSQLSERVER', 1 + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('Handle','ProcessId') + } - { - Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'MSSQLSERVER' -Timeout 1 - } | Should -Throw $errorMessage + Test-DscParameterState @testParameters | Should -Be $true + } - Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $false - } -Scope It -Exactly -Times 1 + It 'Should return false when evaluating a table against a CimInstance and a value is wrong' { + $mockCurrentValues = @{ Handle = '1'; ProcessId = '1000' } - Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $true - } -Scope It -Exactly -Times 1 + $mockWin32ProcessProperties = @{ + Handle = 0 + ProcessId = 1000 } + + $mockNewCimInstanceParameters = @{ + ClassName = 'Win32_Process' + Property = $mockWin32ProcessProperties + Key = 'Handle' + ClientOnly = $true + } + + $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = @('Handle','ProcessId') + } + + Test-DscParameterState @testParameters | Should -Be $false + } + + It 'Should return true when evaluating a hash table containing an array' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = @('1','2') } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + Test-DscParameterState @testParameters | Should -Be $true + } + + It 'Should return false when evaluating a hash table containing an array with wrong values' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = @('A','B') } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + Test-DscParameterState @testParameters | Should -Be $false + } + + It 'Should return false when evaluating a hash table containing an array, but the CurrentValues are missing an array' { + $mockCurrentValues = @{ Example = 'test' } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + Test-DscParameterState @testParameters | Should -Be $false + } + + It 'Should return false when evaluating a hash table containing an array, but the property i CurrentValues is $null' { + $mockCurrentValues = @{ Example = 'test'; SecondExample = $null } + $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + Test-DscParameterState @testParameters | Should -Be $false } } - Context 'Restart-SqlService clustered instance' { - BeforeEach { - Mock -CommandName Connect-SQL -MockWith { - return @{ - Name = 'MSSQLSERVER' - InstanceName = '' - ServiceName = 'MSSQLSERVER' - IsClustered = $true - Status = $mockDynamicStatus + Context -Name 'When passing invalid types for DesiredValues' -Fixture { + It 'Should throw the correct error when DesiredValues is of wrong type' { + $mockCurrentValues = @{ Example = 'something' } + $mockDesiredValues = 'NotHashTable' + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + $mockCorrectErrorMessage = ($script:localizedData.PropertyTypeInvalidForDesiredValues -f $testParameters.DesiredValues.GetType().Name) + { Test-DscParameterState @testParameters } | Should -Throw $mockCorrectErrorMessage + } + + It 'Should write a warning when DesiredValues contain an unsupported type' { + Mock -CommandName Write-Warning -Verifiable + + # This is a dummy type to test with a type that could never be a correct one. + class MockUnknownType + { + [ValidateNotNullOrEmpty()] + [System.String] + $Property1 + + [ValidateNotNullOrEmpty()] + [System.String] + $Property2 + + MockUnknownType() + { } - } -Verifiable -ParameterFilter { ($ServerName -eq 'CLU01') -and ($InstanceName -eq 'MSSQLSERVER') } + } - Mock -CommandName Connect-SQL -MockWith { + $mockCurrentValues = @{ Example = New-Object -TypeName MockUnknownType } + $mockDesiredValues = @{ Example = New-Object -TypeName MockUnknownType } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + Test-DscParameterState @testParameters | Should -Be $false + + Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 + } + } + + Context -Name 'When passing an CimInstance as DesiredValue and ValuesToCheck is $null' -Fixture { + It 'Should throw the correct error' { + $mockCurrentValues = @{ Example = 'something' } + + $mockWin32ProcessProperties = @{ + Handle = 0 + ProcessId = 1000 + } + + $mockNewCimInstanceParameters = @{ + ClassName = 'Win32_Process' + Property = $mockWin32ProcessProperties + Key = 'Handle' + ClientOnly = $true + } + + $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + ValuesToCheck = $null + } + + $mockCorrectErrorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck + { Test-DscParameterState @testParameters } | Should -Throw $mockCorrectErrorMessage + } + } + + Assert-VerifiableMock + } + + Describe 'DscResource.Common\Get-RegistryPropertyValue' -Tag 'GetRegistryPropertyValue' { + BeforeAll { + $mockWrongRegistryPath = 'HKLM:\SOFTWARE\AnyPath' + $mockCorrectRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS' + $mockPropertyName = 'InstanceName' + $mockPropertyValue = 'AnyValue' + } + + Context 'When there are no property in the registry' { + BeforeAll { + Mock -CommandName Get-ItemProperty -MockWith { return @{ - Name = 'NAMEDINSTANCE' - InstanceName = 'NAMEDINSTANCE' - ServiceName = 'NAMEDINSTANCE' - IsClustered = $true - Status = $mockDynamicStatus + 'UnknownProperty' = $mockPropertyValue } - } -Verifiable -ParameterFilter { ($ServerName -eq 'CLU01') -and ($InstanceName -eq 'NAMEDINSTANCE') } + } + } - Mock -CommandName Connect-SQL -MockWith { + It 'Should return $null' { + $result = Get-RegistryPropertyValue -Path $mockWrongRegistryPath -Name $mockPropertyName + $result | Should -BeNullOrEmpty + + Assert-MockCalled Get-ItemProperty -Exactly -Times 1 -Scope It + } + } + + Context 'When the call to Get-ItemProperty throws an error (i.e. when the path does not exist)' { + BeforeAll { + Mock -CommandName Get-ItemProperty -MockWith { + throw 'mocked error' + } + } + + It 'Should not throw an error, but return $null' { + $result = Get-RegistryPropertyValue -Path $mockWrongRegistryPath -Name $mockPropertyName + $result | Should -BeNullOrEmpty + + Assert-MockCalled Get-ItemProperty -Exactly -Times 1 -Scope It + } + } + + Context 'When there are a property in the registry' { + BeforeAll { + $mockGetItemProperty_InstanceName = { return @{ - Name = 'STOPPEDAGENT' - InstanceName = 'STOPPEDAGENT' - ServiceName = 'STOPPEDAGENT' - IsClustered = $true - Status = $mockDynamicStatus + $mockPropertyName = $mockPropertyValue } - } -Verifiable -ParameterFilter { ($ServerName -eq 'CLU01') -and ($InstanceName -eq 'STOPPEDAGENT') } + } + + $mockGetItemProperty_InstanceName_ParameterFilter = { + $Path -eq $mockCorrectRegistryPath ` + -and $Name -eq $mockPropertyName + } + + Mock -CommandName Get-ItemProperty ` + -MockWith $mockGetItemProperty_InstanceName ` + -ParameterFilter $mockGetItemProperty_InstanceName_ParameterFilter } - BeforeAll { - Mock -CommandName Get-CimInstance -MockWith { - @('MSSQLSERVER','NAMEDINSTANCE','STOPPEDAGENT') | ForEach-Object { - $mock = New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList 'MSCluster_Resource','root/MSCluster' + It 'Should return the correct value' { + $result = Get-RegistryPropertyValue -Path $mockCorrectRegistryPath -Name $mockPropertyName + $result | Should -Be $mockPropertyValue - $mock | Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server ($($_))" -TypeName 'String' - $mock | Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server' -TypeName 'String' - $mock | Add-Member -MemberType NoteProperty -Name 'PrivateProperties' -Value @{ InstanceName = $_ } + Assert-MockCalled Get-ItemProperty -Exactly -Times 1 -Scope It + } + } + } + + Describe 'DscResource.Common\Format-Path' -Tag 'FormatPath' { + BeforeAll { + $mockCorrectPath = 'C:\Correct\Path' + $mockPathWithTrailingBackslash = 'C:\Correct\Path\' + $mockPathWithOnlyQualifier = 'M:' + $mockCorrectQualifierPath = 'M:\' + } + + Context 'When there is a path that is wrongly formatted, but now formatting was requested' { + It 'Should return the same wrongly formatted path' { + $result = Format-Path -Path $mockPathWithTrailingBackslash + $result | Should -BeExactly $mockPathWithTrailingBackslash + } + } + + Context 'When there is a path that is formatted correctly, and using TrailingSlash' { + It 'Should return the same path' { + $result = Format-Path -Path $mockCorrectPath -TrailingSlash + $result | Should -BeExactly $mockCorrectPath + } + } + + Context 'When there is a path that has a trailing backslash, and using TrailingSlash' { + It 'Should return the path without trailing backslash' { + $result = Format-Path -Path $mockPathWithTrailingBackslash -TrailingSlash + $result | Should -BeExactly $mockCorrectPath + } + } + + Context 'When there is a path that has only a qualifier, and using TrailingSlash' { + It 'Should return the path with trailing backslash after the qualifier' { + $result = Format-Path -Path $mockPathWithOnlyQualifier -TrailingSlash + $result | Should -BeExactly $mockCorrectQualifierPath + } + } + } + + # Tests only the parts of the code that does not already get tested thru the other tests. + Describe 'DscResource.Common\Copy-ItemWithRobocopy' -Tag 'CopyItemWithRobocopy' { + BeforeAll { + $mockRobocopyExecutableName = 'Robocopy.exe' + $mockRobocopyExecutableVersionWithoutUnbufferedIO = '6.2.9200.00000' + $mockRobocopyExecutableVersionWithUnbufferedIO = '6.3.9600.16384' + $mockRobocopyExecutableVersion = '' # Set dynamically during runtime + $mockRobocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' + $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' + $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' + $mockRobocopyArgumentUseUnbufferedIO = '/J' + $mockRobocopyArgumentSourcePath = 'C:\Source\SQL2016' + $mockRobocopyArgumentDestinationPath = 'D:\Temp' + $mockRobocopyArgumentSourcePathWithSpaces = 'C:\Source\SQL2016 STD SP1' + $mockRobocopyArgumentDestinationPathWithSpaces = 'D:\Temp\DSC SQL2016' + + $mockGetCommand = { + return @( + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockRobocopyExecutableName -PassThru | + Add-Member -MemberType ScriptProperty -Name FileVersionInfo -Value { + return @( ( New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'ProductVersion' -Value $mockRobocopyExecutableVersion -PassThru -Force + ) ) + } -PassThru -Force + ) + ) + } + + $mockStartSqlSetupProcessExpectedArgument = '' # Set dynamically during runtime + $mockStartSqlSetupProcessExitCode = 0 # Set dynamically during runtime + + $mockStartSqlSetupProcess_Robocopy = { + if ( $ArgumentList -cne $mockStartSqlSetupProcessExpectedArgument ) + { + throw "Expected arguments was not the same as the arguments in the function call.`nExpected: '$mockStartSqlSetupProcessExpectedArgument' `n But was: '$ArgumentList'" + } + + return New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value 0 -PassThru -Force + } + + $mockStartSqlSetupProcess_Robocopy_WithExitCode = { + return New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value $mockStartSqlSetupProcessExitCode -PassThru -Force + } + } + + Context 'When Copy-ItemWithRobocopy is called it should return the correct arguments' { + BeforeEach { + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy -Verifiable + $mockRobocopyArgumentSourcePathQuoted = '"{0}"' -f $mockRobocopyArgumentSourcePath + $mockRobocopyArgumentDestinationPathQuoted = '"{0}"' -f $mockRobocopyArgumentDestinationPath + } + + + It 'Should use Unbuffered IO when copying' { + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO + + $mockStartSqlSetupProcessExpectedArgument = + $mockRobocopyArgumentSourcePathQuoted, + $mockRobocopyArgumentDestinationPathQuoted, + $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty, + $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, + $mockRobocopyArgumentUseUnbufferedIO, + $mockRobocopyArgumentSilent -join ' ' + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + + It 'Should not use Unbuffered IO when copying' { + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithoutUnbufferedIO + + $mockStartSqlSetupProcessExpectedArgument = + $mockRobocopyArgumentSourcePathQuoted, + $mockRobocopyArgumentDestinationPathQuoted, + $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty, + $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, + '', + $mockRobocopyArgumentSilent -join ' ' + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + } + + Context 'When Copy-ItemWithRobocopy throws an exception it should return the correct error messages' { + BeforeEach { + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO + + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy_WithExitCode -Verifiable + } + + It 'Should throw the correct error message when error code is 8' { + $mockStartSqlSetupProcessExitCode = 8 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Throw "Robocopy reported errors when copying files. Error code: $mockStartSqlSetupProcessExitCode." + + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + + It 'Should throw the correct error message when error code is 16' { + $mockStartSqlSetupProcessExitCode = 16 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Throw "Robocopy reported errors when copying files. Error code: $mockStartSqlSetupProcessExitCode." + + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + + It 'Should throw the correct error message when error code is greater than 7 (but not 8 or 16)' { + $mockStartSqlSetupProcessExitCode = 9 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Throw "Robocopy reported that failures occurred when copying files. Error code: $mockStartSqlSetupProcessExitCode." + + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + } + + Context 'When Copy-ItemWithRobocopy is called and finishes successfully it should return the correct exit code' { + BeforeEach { + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO + + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy_WithExitCode -Verifiable + } + + AfterEach { + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + + It 'Should finish successfully with exit code 1' { + $mockStartSqlSetupProcessExitCode = 1 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + + It 'Should finish successfully with exit code 2' { + $mockStartSqlSetupProcessExitCode = 2 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + + It 'Should finish successfully with exit code 3' { + $mockStartSqlSetupProcessExitCode = 3 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + } + } + + Context 'When Copy-ItemWithRobocopy is called with spaces in paths and finishes successfully it should return the correct exit code' { + BeforeEach { + $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO + + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy_WithExitCode -Verifiable + } + + AfterEach { + Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It + } + + It 'Should finish successfully with exit code 1' { + $mockStartSqlSetupProcessExitCode = 1 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePathWithSpaces + DestinationPath = $mockRobocopyArgumentDestinationPathWithSpaces + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + + } + + It 'Should finish successfully with exit code 2' { + $mockStartSqlSetupProcessExitCode = 2 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePathWithSpaces + DestinationPath = $mockRobocopyArgumentDestinationPathWithSpaces + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + + } + + It 'Should finish successfully with exit code 3' { + $mockStartSqlSetupProcessExitCode = 3 + + $copyItemWithRobocopyParameter = @{ + Path = $mockRobocopyArgumentSourcePathWithSpaces + DestinationPath = $mockRobocopyArgumentDestinationPathWithSpaces + } + + { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw + } + } + } + + Describe 'DscResource.Common\Get-TemporaryFolder' -Tag 'GetTemporaryFolder' { + BeforeAll { + $mockExpectedTempPath = [IO.Path]::GetTempPath() + } + + Context 'When using Get-TemporaryFolder' { + It 'Should return the correct temporary path' { + Get-TemporaryFolder | Should -BeExactly $mockExpectedTempPath + } + } + } + + Describe 'DscResource.Common\Invoke-InstallationMediaCopy' -Tag 'InvokeInstallationMediaCopy' { + BeforeAll { + $mockSourcePathUNC = '\\server\share' + $mockSourcePathUNCWithLeaf = '\\server\share\leaf' + $mockSourcePathGuid = 'cc719562-0f46-4a16-8605-9f8a47c70402' + $mockDestinationPath = 'TestDrive:\' + + $mockShareCredentialUserName = 'COMPANY\SqlAdmin' + $mockShareCredentialPassword = 'dummyPassW0rd' + $mockShareCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @( + $mockShareCredentialUserName, + ($mockShareCredentialPassword | ConvertTo-SecureString -AsPlainText -Force) + ) + + $mockGetTemporaryFolder = { + return $mockDestinationPath + } - return $mock - } - } -Verifiable -ParameterFilter { ($ClassName -eq 'MSCluster_Resource') -and ($Filter -eq "Type = 'SQL Server'") } + $mockNewGuid = { + return New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Guid' -Value $mockSourcePathGuid -PassThru -Force + } - Mock -CommandName Get-CimAssociatedInstance -MockWith { - $mock = New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList 'MSCluster_Resource','root/MSCluster' + Mock -CommandName Connect-UncPath + Mock -CommandName Disconnect-UncPath + Mock -CommandName Copy-ItemWithRobocopy + Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder + Mock -CommandName New-Guid -MockWith $mockNewGuid + } - $mock | Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server Agent ($($InputObject.PrivateProperties.InstanceName))" -TypeName 'String' - $mock | Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server Agent' -TypeName 'String' - $mock | Add-Member -MemberType NoteProperty -Name 'State' -Value (@{ $true = 3; $false = 2 }[($InputObject.PrivateProperties.InstanceName -eq 'STOPPEDAGENT')]) -TypeName 'Int32' + Context 'When invoking installation media copy, using SourcePath containing leaf' { + It 'Should call the correct mocks' { + { + $invokeInstallationMediaCopyParameters = @{ + SourcePath = $mockSourcePathUNCWithLeaf + SourceCredential = $mockShareCredential + } - return $mock - } -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } + Invoke-InstallationMediaCopy @invokeInstallationMediaCopyParameters + } | Should -Not -Throw - Mock -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Verifiable - Mock -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Verifiable + Assert-MockCalled -CommandName Connect-UncPath -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName New-Guid -Exactly -Times 0 -Scope 'It' + Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Disconnect-UncPath -Exactly -Times 1 -Scope 'It' } - $mockDynamicStatus = 'Online' + It 'Should return the correct destination path' { + $invokeInstallationMediaCopyParameters = @{ + SourcePath = $mockSourcePathUNCWithLeaf + SourceCredential = $mockShareCredential + PassThru = $true + } - It 'Should restart SQL Server and SQL Agent resources for a clustered default instance' { - { Restart-SqlService -SQLServer 'CLU01' } | Should -Not -Throw + $invokeInstallationMediaCopyResult = Invoke-InstallationMediaCopy @invokeInstallationMediaCopyParameters - Assert-MockCalled -CommandName Connect-SQL { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $false - } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 + $invokeInstallationMediaCopyResult | Should -Be ('{0}leaf' -f $mockDestinationPath) } + } - It 'Should restart SQL Server and SQL Agent resources for a clustered named instance' { - { Restart-SqlService -SQLServer 'CLU01' -SQLInstanceName 'NAMEDINSTANCE' } | Should -Not -Throw + Context 'When invoking installation media copy, using SourcePath without a leaf' { + It 'Should call the correct mocks' { + { + $invokeInstallationMediaCopyParameters = @{ + SourcePath = $mockSourcePathUNC + SourceCredential = $mockShareCredential + } - Assert-MockCalled -CommandName Connect-SQL { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $false - } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 + Invoke-InstallationMediaCopy @invokeInstallationMediaCopyParameters + } | Should -Not -Throw + + Assert-MockCalled -CommandName Connect-UncPath -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName New-Guid -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Disconnect-UncPath -Exactly -Times 1 -Scope 'It' } - It 'Should not try to restart a SQL Agent resource that is not online' { - { Restart-SqlService -SQLServer 'CLU01' -SQLInstanceName 'STOPPEDAGENT' } | Should -Not -Throw + It 'Should return the correct destination path' { + $invokeInstallationMediaCopyParameters = @{ + SourcePath = $mockSourcePathUNC + SourceCredential = $mockShareCredential + PassThru = $true + } - Assert-MockCalled -CommandName Connect-SQL { - $PSBoundParameters.ContainsKey('ErrorAction') -eq $false - } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 1 + $invokeInstallationMediaCopyResult = Invoke-InstallationMediaCopy @invokeInstallationMediaCopyParameters + $invokeInstallationMediaCopyResult | Should -Be ('{0}{1}' -f $mockDestinationPath, $mockSourcePathGuid) } } } - Describe 'Testing Connect-SQLAnalysis' { - BeforeEach { - Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable - Mock -CommandName New-Object ` - -MockWith $mockNewObject_MicrosoftAnalysisServicesServer ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter ` - -Verifiable + Describe 'DscResource.Common\Connect-UncPath' -Tag 'ConnectUncPath' { + BeforeAll { + $mockSourcePathUNC = '\\server\share' + + $mockShareCredentialUserName = 'COMPANY\SqlAdmin' + $mockShareCredentialPassword = 'dummyPassW0rd' + $mockShareCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @( + $mockShareCredentialUserName, + ($mockShareCredentialPassword | ConvertTo-SecureString -AsPlainText -Force) + ) + + Mock -CommandName New-SmbMapping -MockWith { + return @{ + RemotePath = $mockSourcePathUNC + } + } } - Context 'When connecting to the default instance using Windows Authentication' { - It 'Should not throw when connecting' { - $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME" + Context 'When connecting to a UNC path without credentials (using current credentials)' { + It 'Should call the correct mocks' { + { + $connectUncPathParameters = @{ + RemotePath = $mockSourcePathUNC + } - { Connect-SQLAnalysis } | Should -Not -Throw + Connect-UncPath @connectUncPathParameters + } | Should -Not -Throw - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + Assert-MockCalled -CommandName New-SmbMapping -ParameterFilter { + $RemotePath -eq $mockSourcePathUNC ` + -and $PSBoundParameters.ContainsKey('UserName') -eq $false + } -Exactly -Times 1 -Scope 'It' } } - Context 'When connecting to the named instance using Windows Authentication' { - It 'Should not throw when connecting' { - $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME\$mockInstanceName" + Context 'When connecting to a UNC path with specific credentials' { + It 'Should call the correct mocks' { + { + $connectUncPathParameters = @{ + RemotePath = $mockSourcePathUNC + SourceCredential = $mockShareCredential + } - { Connect-SQLAnalysis -SQLInstanceName $mockInstanceName } | Should -Not -Throw + Connect-UncPath @connectUncPathParameters + } | Should -Not -Throw - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + Assert-MockCalled -CommandName New-SmbMapping -ParameterFilter { + $RemotePath -eq $mockSourcePathUNC ` + -and $UserName -eq $mockShareCredentialUserName + } -Exactly -Times 1 -Scope 'It' } } - Context 'When connecting to the named instance using Windows Authentication impersonation' { - It 'Should not throw when connecting' { - $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME\$mockInstanceName;User ID=$mockSetupCredentialUserName;Password=$mockSetupCredentialPassword" - - { Connect-SQLAnalysis -SQLInstanceName $mockInstanceName -SetupCredential $mockSetupCredential } | Should -Not -Throw + Context 'When connecting to a UNC path and using parameter PassThru' { + It 'Should return the correct MSFT_SmbMapping object' { + $connectUncPathParameters = @{ + RemotePath = $mockSourcePathUNC + SourceCredential = $mockShareCredential + PassThru = $true + } - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + $connectUncPathResult = Connect-UncPath @connectUncPathParameters + $connectUncPathResult.RemotePath | Should -Be $mockSourcePathUNC } } + } - Context 'When connecting to the default instance using the correct service instance but does not return a correct Analysis Service object' { - It 'Should throw the correct error' { - $mockExpectedDataSource = '' + Describe 'DscResource.Common\Disconnect-UncPath' -Tag 'DisconnectUncPath' { + BeforeAll { + $mockSourcePathUNC = '\\server\share' - Mock -CommandName New-Object ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter ` - -Verifiable + Mock -CommandName Remove-SmbMapping + } - $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToAnalysisServicesInstance -f $env:COMPUTERNAME) - { Connect-SQLAnalysis } | Should -Throw $mockCorrectErrorMessage + Context 'When disconnecting from an UNC path' { + It 'Should call the correct mocks' { + { + $disconnectUncPathParameters = @{ + RemotePath = $mockSourcePathUNC + } - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + Disconnect-UncPath @disconnectUncPathParameters + } | Should -Not -Throw + + Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope 'It' } } + } - Context 'When connecting to the default instance using a Analysis Service instance that does not exist' { - It 'Should throw the correct error' { - $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME" + Describe 'DscResource.Common\Test-PendingRestart' -Tag 'TestPendingRestart' { + Context 'When there is a pending reboot' { + BeforeAll { + Mock -CommandName Get-RegistryPropertyValue -MockWith { + return 'AnyValue' + } + } - # Force the mock of Connect() method to throw 'Unable to connect.' - $mockThrowInvalidOperation = $true + It 'Should return $true' { + $testPendingRestartResult = Test-PendingRestart + $testPendingRestartResult | Should -BeTrue - $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToAnalysisServicesInstance -f $env:COMPUTERNAME) - { Connect-SQLAnalysis } | Should -Throw $mockCorrectErrorMessage + Assert-MockCalled -CommandName Get-RegistryPropertyValue -Exactly -Times 1 -Scope 'It' + } + } - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + Context 'When there are no pending reboot' { + BeforeAll { + Mock -CommandName Get-RegistryPropertyValue + } - # Setting it back to the default so it does not disturb other tests. - $mockThrowInvalidOperation = $false + It 'Should return $true' { + $testPendingRestartResult = Test-PendingRestart + $testPendingRestartResult | Should -BeFalse + + Assert-MockCalled -CommandName Get-RegistryPropertyValue -Exactly -Times 1 -Scope 'It' } } + } - # This test is to test the mock so that it throws correct when data source is not the expected data source - Context 'When connecting to the named instance using another data source then expected' { - It 'Should throw the correct error' { - $mockExpectedDataSource = "Force wrong data source" - - $testParameters = @{ - SQLServer = 'DummyHost' - SQLInstanceName = $mockInstanceName + Describe 'DscResource.Common\Start-SqlSetupProcess' -Tag 'StartSqlSetupProcess' { + Context 'When starting a process successfully' { + It 'Should return exit code 0' { + $startSqlSetupProcessParameters = @{ + FilePath = 'powershell.exe' + ArgumentList = '-Command &{Start-Sleep -Seconds 2}' + Timeout = 30 } - $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToAnalysisServicesInstance -f "$($testParameters.SQLServer)\$($testParameters.SQLInstanceName)") - { Connect-SQLAnalysis @testParameters } | Should -Throw $mockCorrectErrorMessage - - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + $processExitCode = Start-SqlSetupProcess @startSqlSetupProcessParameters + $processExitCode | Should -BeExactly 0 } } - Assert-VerifiableMock + Context 'When starting a process and the process does not finish before the timeout period' { + It 'Should throw an error message' { + $startSqlSetupProcessParameters = @{ + FilePath = 'powershell.exe' + ArgumentList = '-Command &{Start-Sleep -Seconds 4}' + Timeout = 2 + } + + { Start-SqlSetupProcess @startSqlSetupProcessParameters } | Should -Throw -ErrorId 'ProcessNotTerminated,Microsoft.PowerShell.Commands.WaitProcessCommand' + } + } } - Describe 'Testing Invoke-Query' { - $mockExpectedQuery = '' + Describe 'DscResource.Common\Restart-SqlService' -Tag 'RestartSqlService' { + Context 'Restart-SqlService standalone instance' { + BeforeEach { + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'MSSQLSERVER' + InstanceName = '' + ServiceName = 'MSSQLSERVER' + Status = $mockDynamicStatus + IsClustered = $false + } + } -Verifiable -ParameterFilter { $InstanceName -eq 'MSSQLSERVER' } - $mockConnectSql = { - return @( - ( - New-Object -TypeName PSObject -Property @{ - Databases = @{ - 'master' = ( - New-Object -TypeName PSObject -Property @{ Name = 'master' } | - Add-Member -MemberType ScriptMethod -Name ExecuteNonQuery -Value { - param - ( - [Parameter()] - [System.String] - $sqlCommand - ) - - if ( $sqlCommand -ne $mockExpectedQuery ) - { - throw - } - } -PassThru | - Add-Member -MemberType ScriptMethod -Name ExecuteWithResults -Value { - param - ( - [Parameter()] - [System.String] - $sqlCommand - ) - - if ( $sqlCommand -ne $mockExpectedQuery ) - { - throw - } - - return New-Object -TypeName System.Data.DataSet - } -PassThru - ) - } + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'NOCLUSTERCHECK' + InstanceName = 'NOCLUSTERCHECK' + ServiceName = 'NOCLUSTERCHECK' + Status = $mockDynamicStatus + IsClustered = $true } - ) - ) - } + } -Verifiable -ParameterFilter { $InstanceName -eq 'NOCLUSTERCHECK' } - BeforeEach { - Mock -CommandName Connect-SQL -MockWith $mockConnectSql -ModuleName $script:dscResourceName -Verifiable - Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable - } + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'NOCONNECT' + InstanceName = 'NOCONNECT' + ServiceName = 'NOCONNECT' + Status = $mockDynamicStatus + IsClustered = $true + } + } -Verifiable -ParameterFilter { $InstanceName -eq 'NOCONNECT' } - $queryParams = @{ - SQLServer = 'Server1' - SQLInstanceName = 'MSSQLSERVER' - Database = 'master' - Query = '' - } + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'NOAGENT' + InstanceName = 'NOAGENT' + ServiceName = 'NOAGENT' + Status = $mockDynamicStatus + } + } -Verifiable -ParameterFilter { $InstanceName -eq 'NOAGENT' } - Context 'Execute a query with no results' { - It 'Should execute the query silently' { - $queryParams.Query = "EXEC sp_configure 'show advanced option', '1'" - $mockExpectedQuery = $queryParams.Query.Clone() + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'STOPPEDAGENT' + InstanceName = 'STOPPEDAGENT' + ServiceName = 'STOPPEDAGENT' + Status = $mockDynamicStatus + } + } -Verifiable -ParameterFilter { $InstanceName -eq 'STOPPEDAGENT' } + } - { Invoke-Query @queryParams } | Should -Not -Throw + BeforeAll { + ## SQL instance with running SQL Agent Service + Mock -CommandName Get-Service -MockWith { + return @{ + Name = 'MSSQLSERVER' + DisplayName = 'Microsoft SQL Server (MSSQLSERVER)' + DependentServices = @( + @{ + Name = 'SQLSERVERAGENT' + DisplayName = 'SQL Server Agent (MSSQLSERVER)' + Status = 'Running' + DependentServices = @() + } + ) + } + } -Verifiable -ParameterFilter { $Name -eq 'MSSQLSERVER' } + + ## SQL instance with no installed SQL Agent Service + Mock -CommandName Get-Service -MockWith { + return @{ + Name = 'MSSQL$NOAGENT' + DisplayName = 'Microsoft SQL Server (NOAGENT)' + DependentServices = @() + } + } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$NOAGENT' } + + ## SQL instance with no installed SQL Agent Service + Mock -CommandName Get-Service -MockWith { + return @{ + Name = 'MSSQL$NOCLUSTERCHECK' + DisplayName = 'Microsoft SQL Server (NOCLUSTERCHECK)' + DependentServices = @() + } + } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$NOCLUSTERCHECK' } + + ## SQL instance with no installed SQL Agent Service + Mock -CommandName Get-Service -MockWith { + return @{ + Name = 'MSSQL$NOCONNECT' + DisplayName = 'Microsoft SQL Server (NOCONNECT)' + DependentServices = @() + } + } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$NOCONNECT' } + + ## SQL instance with stopped SQL Agent Service + Mock -CommandName Get-Service -MockWith { + return @{ + Name = 'MSSQL$STOPPEDAGENT' + DisplayName = 'Microsoft SQL Server (STOPPEDAGENT)' + DependentServices = @( + @{ + Name = 'SQLAGENT$STOPPEDAGENT' + DisplayName = 'SQL Server Agent (STOPPEDAGENT)' + Status = 'Stopped' + DependentServices = @() + } + ) + } + } -Verifiable -ParameterFilter { $Name -eq 'MSSQL$STOPPEDAGENT' } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Mock -CommandName Restart-Service -Verifiable + Mock -CommandName Start-Service -Verifiable } - It 'Should throw the correct error, ExecuteNonQueryFailed, when executing the query fails' { - $queryParams.Query = 'BadQuery' + $mockDynamicStatus = 'Online' - { Invoke-Query @queryParams } | Should -Throw ($script:localizedData.ExecuteNonQueryFailed -f $queryParams.Database) + It 'Should restart SQL Service and running SQL Agent service' { + { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'MSSQLSERVER' } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $false + } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 1 } - } - Context 'Execute a query with results' { - It 'Should execute the query and return a result set' { - $queryParams.Query = 'SELECT name FROM sys.databases' - $mockExpectedQuery = $queryParams.Query.Clone() + It 'Should restart SQL Service, and not do cluster cluster check' { + Mock -CommandName Get-CimInstance - Invoke-Query @queryParams -WithResults | Should -Not -BeNullOrEmpty + { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'NOCLUSTERCHECK' -SkipClusterCheck } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 + Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 0 } - It 'Should throw the correct error, ExecuteQueryWithResultsFailed, when executing the query fails' { - $queryParams.Query = 'BadQuery' + It 'Should restart SQL Service, and not do cluster cluster check nor check online status' { + Mock -CommandName Get-CimInstance - { Invoke-Query @queryParams -WithResults } | Should -Throw ($script:localizedData.ExecuteQueryWithResultsFailed -f $queryParams.Database) + { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'NOCONNECT' -SkipClusterCheck -SkipWaitForOnline } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Connect-SQL -Scope It -Exactly -Times 0 + Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 + Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 0 } - } - } - - Describe "Testing Update-AvailabilityGroupReplica" { - Context 'When the Availability Group Replica is altered' { - It 'Should silently alter the Availability Group Replica' { - $availabilityReplica = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica - { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should -Not -Throw + It 'Should restart SQL Service and not try to restart missing SQL Agent service' { + { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'NOAGENT' } | Should -Not -Throw + Assert-MockCalled -CommandName Connect-SQL { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $false + } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 } - It 'Should throw the correct error, AlterAvailabilityGroupReplicaFailed, when altering the Availability Group Replica fails' { - $availabilityReplica = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica - $availabilityReplica.Name = 'AlterFailed' + It 'Should restart SQL Service and not try to restart stopped SQL Agent service' { + { Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'STOPPEDAGENT' } | Should -Not -Throw - { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should -Throw ($script:localizedData.AlterAvailabilityGroupReplicaFailed -f $availabilityReplica.Name) + Assert-MockCalled -CommandName Connect-SQL { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $false + } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Restart-Service -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 0 } - } - } - - Describe "Testing Test-LoginEffectivePermissions" { - $mockAllServerPermissionsPresent = @( - 'Connect SQL', - 'Alter Any Availability Group', - 'View Server State' - ) + Context 'When it fails to connect to the instance within the timeout period' { + BeforeEach { + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'MSSQLSERVER' + InstanceName = '' + ServiceName = 'MSSQLSERVER' + Status = $mockDynamicStatus + } + } -Verifiable -ParameterFilter { $InstanceName -eq 'MSSQLSERVER' } + } - $mockServerPermissionsMissing = @( - 'Connect SQL', - 'View Server State' - ) + $mockDynamicStatus = 'Offline' - $mockAllLoginPermissionsPresent = @( - 'View Definition', - 'Impersonate' - ) + It 'Should throw the correct error message' { + $errorMessage = $localizedData.FailedToConnectToInstanceTimeout -f $env:ComputerName, 'MSSQLSERVER', 1 - $mockLoginPermissionsMissing = @( - 'View Definition' - ) + { + Restart-SqlService -SQLServer $env:ComputerName -SQLInstanceName 'MSSQLSERVER' -Timeout 1 + } | Should -Throw $errorMessage - $mockInvokeQueryPermissionsSet = @() # Will be set dynamically in the check + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $false + } -Scope It -Exactly -Times 1 - $mockInvokeQueryPermissionsResult = { - return New-Object -TypeName PSObject -Property @{ - Tables = @{ - Rows = @{ - permission_name = $mockInvokeQueryPermissionsSet - } + Assert-MockCalled -CommandName Connect-SQL -ParameterFilter { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $true + } -Scope It -Exactly -Times 1 } } } - $testLoginEffectiveServerPermissionsParams = @{ - SQLServer = 'Server1' - SQLInstanceName = 'MSSQLSERVER' - Login = 'NT SERVICE\ClusSvc' - Permissions = @() - } + Context 'Restart-SqlService clustered instance' { + BeforeEach { + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'MSSQLSERVER' + InstanceName = '' + ServiceName = 'MSSQLSERVER' + IsClustered = $true + Status = $mockDynamicStatus + } + } -Verifiable -ParameterFilter { ($ServerName -eq 'CLU01') -and ($InstanceName -eq 'MSSQLSERVER') } - $testLoginEffectiveLoginPermissionsParams = @{ - SQLServer = 'Server1' - SQLInstanceName = 'MSSQLSERVER' - Login = 'NT SERVICE\ClusSvc' - Permissions = @() - SecurableClass = 'LOGIN' - SecurableName = 'Login1' - } + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'NAMEDINSTANCE' + InstanceName = 'NAMEDINSTANCE' + ServiceName = 'NAMEDINSTANCE' + IsClustered = $true + Status = $mockDynamicStatus + } + } -Verifiable -ParameterFilter { ($ServerName -eq 'CLU01') -and ($InstanceName -eq 'NAMEDINSTANCE') } - BeforeEach { - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryPermissionsResult -Verifiable - } + Mock -CommandName Connect-SQL -MockWith { + return @{ + Name = 'STOPPEDAGENT' + InstanceName = 'STOPPEDAGENT' + ServiceName = 'STOPPEDAGENT' + IsClustered = $true + Status = $mockDynamicStatus + } + } -Verifiable -ParameterFilter { ($ServerName -eq 'CLU01') -and ($InstanceName -eq 'STOPPEDAGENT') } + } - Context 'When all of the permissions are present' { - It 'Should return $true when the desired server permissions are present' { - $mockInvokeQueryPermissionsSet = $mockAllServerPermissionsPresent.Clone() - $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() + BeforeAll { + Mock -CommandName Get-CimInstance -MockWith { + @('MSSQLSERVER','NAMEDINSTANCE','STOPPEDAGENT') | ForEach-Object { + $mock = New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList 'MSCluster_Resource','root/MSCluster' - Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $true + $mock | Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server ($($_))" -TypeName 'String' + $mock | Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server' -TypeName 'String' + $mock | Add-Member -MemberType NoteProperty -Name 'PrivateProperties' -Value @{ InstanceName = $_ } - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly - } + return $mock + } + } -Verifiable -ParameterFilter { ($ClassName -eq 'MSCluster_Resource') -and ($Filter -eq "Type = 'SQL Server'") } - It 'Should return $true when the desired login permissions are present' { - $mockInvokeQueryPermissionsSet = $mockAllLoginPermissionsPresent.Clone() - $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() + Mock -CommandName Get-CimAssociatedInstance -MockWith { + $mock = New-Object -TypeName Microsoft.Management.Infrastructure.CimInstance -ArgumentList 'MSCluster_Resource','root/MSCluster' - Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $true + $mock | Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server Agent ($($InputObject.PrivateProperties.InstanceName))" -TypeName 'String' + $mock | Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server Agent' -TypeName 'String' + $mock | Add-Member -MemberType NoteProperty -Name 'State' -Value (@{ $true = 3; $false = 2 }[($InputObject.PrivateProperties.InstanceName -eq 'STOPPEDAGENT')]) -TypeName 'Int32' - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + return $mock + } -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } + + Mock -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Verifiable + Mock -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Verifiable } - } - Context 'When a permission is missing' { - It 'Should return $false when the desired server permissions are not present' { - $mockInvokeQueryPermissionsSet = $mockServerPermissionsMissing.Clone() - $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() + $mockDynamicStatus = 'Online' - Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $false + It 'Should restart SQL Server and SQL Agent resources for a clustered default instance' { + { Restart-SqlService -SQLServer 'CLU01' } | Should -Not -Throw - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $false + } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 } - It 'Should return $false when the specified login has no server permissions assigned' { - $mockInvokeQueryPermissionsSet = @() - $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() + It 'Should restart SQL Server and SQL Agent resources for a clustered named instance' { + { Restart-SqlService -SQLServer 'CLU01' -SQLInstanceName 'NAMEDINSTANCE' } | Should -Not -Throw - Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $false + Assert-MockCalled -CommandName Connect-SQL { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $false + } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 + } - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + It 'Should not try to restart a SQL Agent resource that is not online' { + { Restart-SqlService -SQLServer 'CLU01' -SQLInstanceName 'STOPPEDAGENT' } | Should -Not -Throw + + Assert-MockCalled -CommandName Connect-SQL { + $PSBoundParameters.ContainsKey('ErrorAction') -eq $false + } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 1 } + } + } + + Describe 'DscResource.Common\Connect-SQLAnalysis' -Tag 'ConnectSQLAnalysis' { + BeforeAll { + $mockInstanceName = 'TEST' - It 'Should return $false when the desired login permissions are not present' { - $mockInvokeQueryPermissionsSet = $mockLoginPermissionsMissing.Clone() - $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() + $mockNewObject_MicrosoftAnalysisServicesServer = { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name Connect -Value { + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $dataSource + ) - Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $false + if ($dataSource -ne $mockExpectedDataSource) + { + throw ("Datasource was expected to be '{0}', but was '{1}'." -f $mockExpectedDataSource,$dataSource) + } - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + if ($mockThrowInvalidOperation) + { + throw 'Unable to connect.' + } + } -PassThru -Force } - It 'Should return $false when the specified login has no login permissions assigned' { - $mockInvokeQueryPermissionsSet = @() - $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() - - Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $false + $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter = { + $TypeName -eq 'Microsoft.AnalysisServices.Server' + } - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + $mockThrowLocalizedMessage = { + throw $Message } + + $mockSetupCredentialUserName = 'TestUserName12345' + $mockSetupCredentialPassword = 'StrongOne7.' + $mockSetupCredentialSecurePassword = ConvertTo-SecureString -String $mockSetupCredentialPassword -AsPlainText -Force + $mockSetupCredential = New-Object -TypeName PSCredential -ArgumentList ($mockSetupCredentialUserName, $mockSetupCredentialSecurePassword) } - } - <# - This is the path to the latest version of SQLPS, to test that only the - newest SQLPS module is returned. - #> - $sqlPsLatestModulePath = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + BeforeEach { + Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable + Mock -CommandName New-Object ` + -MockWith $mockNewObject_MicrosoftAnalysisServicesServer ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter ` + -Verifiable + } - <# - For SQLPS module this should be the root of the module. - The .psd1 file is parsed from the module full path in the code. - #> - $sqlPsExpectedModulePath = Split-Path -Path $sqlPsLatestModulePath -Parent + Context 'When connecting to the default instance using Windows Authentication' { + It 'Should not throw when connecting' { + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME" + { Connect-SQLAnalysis } | Should -Not -Throw - $mockImportModule = { - if ($Name -ne $mockExpectedModuleNameToImport) - { - throw ('Wrong module was loaded. Expected {0}, but was {1}.' -f $mockExpectedModuleNameToImport, $Name[0]) + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + } } - switch ($Name) - { - 'SqlServer' - { - $importModuleResult = @{ - ModuleType = 'Script' - Version = '21.0.17279' - Name = $Name - } - } + Context 'When connecting to the named instance using Windows Authentication' { + It 'Should not throw when connecting' { + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME\$mockInstanceName" - $sqlPsExpectedModulePath - { - # Can not use $Name because that contain the path to the module manifest. - $importModuleResult = @( - @{ - ModuleType = 'Script' - Version = '0.0' - # Intentionally formatted to correctly mimic a real run. - Name = 'Sqlps' - Path = $sqlPsLatestModulePath - } - @{ - ModuleType = 'Manifest' - Version = '1.0' - # Intentionally formatted to correctly mimic a real run. - Name = 'sqlps' - Path = $sqlPsLatestModulePath - } - ) + { Connect-SQLAnalysis -SQLInstanceName $mockInstanceName } | Should -Not -Throw + + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } } - return $importModuleResult - } - - $mockGetModuleSqlServer = { - # Return an array to test so that the latest version is only imported. - return @( - New-Object -TypeName PSObject -Property @{ - Name = 'SqlServer' - Version = [Version] '1.0' - } + Context 'When connecting to the named instance using Windows Authentication impersonation' { + It 'Should not throw when connecting' { + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME\$mockInstanceName;User ID=$mockSetupCredentialUserName;Password=$mockSetupCredentialPassword" - New-Object -TypeName PSObject -Property @{ - Name = 'SqlServer' - Version = [Version] '2.0' - } - ) - } + { Connect-SQLAnalysis -SQLInstanceName $mockInstanceName -SetupCredential $mockSetupCredential } | Should -Not -Throw - $mockGetModuleSqlPs = { - # Return an array to test so that the latest version is only imported. - return @( - New-Object -TypeName PSObject -Property @{ - Name = 'SQLPS' - # This is a path to an older version of SQL PS than $sqlPsLatestModulePath. - Path = 'C:\Program Files (x86)\Microsoft SQL Server\120\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } + } - New-Object -TypeName PSObject -Property @{ - Name = 'SQLPS' - Path = $sqlPsLatestModulePath - } - ) - } + Context 'When connecting to the default instance using the correct service instance but does not return a correct Analysis Service object' { + It 'Should throw the correct error' { + $mockExpectedDataSource = '' - $mockGetModule_SqlServer_ParameterFilter = { - $FullyQualifiedName.Name -eq 'SqlServer' -and $ListAvailable -eq $true - } + Mock -CommandName New-Object ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter ` + -Verifiable - $mockGetModule_SQLPS_ParameterFilter = { - $FullyQualifiedName.Name -eq 'SQLPS' -and $ListAvailable -eq $true - } + $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToAnalysisServicesInstance -f $env:COMPUTERNAME) + { Connect-SQLAnalysis } | Should -Throw $mockCorrectErrorMessage - Describe 'Testing Import-SQLPSModule' -Tag 'ImportSQLPSModule' { - BeforeEach { - Mock -CommandName Push-Location -Verifiable - Mock -CommandName Pop-Location -Verifiable - Mock -CommandName Import-Module -MockWith $mockImportModule -Verifiable - Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + } } - Context 'When module SqlServer is already loaded into the session' { - BeforeAll { - Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SqlServer' - } - } - } + Context 'When connecting to the default instance using a Analysis Service instance that does not exist' { + It 'Should throw the correct error' { + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME" - It 'Should use the already loaded module and not call Import-Module' { - { Import-SQLPSModule } | Should -Not -Throw + # Force the mock of Connect() method to throw 'Unable to connect.' + $mockThrowInvalidOperation = $true - Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It + $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToAnalysisServicesInstance -f $env:COMPUTERNAME) + { Connect-SQLAnalysis } | Should -Throw $mockCorrectErrorMessage + + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter + + # Setting it back to the default so it does not disturb other tests. + $mockThrowInvalidOperation = $false } } - Context 'When module SQLPS is already loaded into the session' { - BeforeAll { - Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SQLPS' - } + # This test is to test the mock so that it throws correct when data source is not the expected data source + Context 'When connecting to the named instance using another data source then expected' { + It 'Should throw the correct error' { + $mockExpectedDataSource = "Force wrong data source" + + $testParameters = @{ + SQLServer = 'DummyHost' + SQLInstanceName = $mockInstanceName } - } - It 'Should use the already loaded module and not call Import-Module' { - { Import-SQLPSModule } | Should -Not -Throw + $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToAnalysisServicesInstance -f "$($testParameters.SQLServer)\$($testParameters.SQLInstanceName)") + { Connect-SQLAnalysis @testParameters } | Should -Throw $mockCorrectErrorMessage - Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } } - Context 'When module SqlServer exists, but not loaded into the session' { - BeforeAll { - Mock -CommandName Get-Module -ParameterFilter { - $PSBoundParameters.ContainsKey('Name') -eq $true - } + Assert-VerifiableMock + } - $mockExpectedModuleNameToImport = 'SqlServer' + Describe 'DscResource.Common\Invoke-Query' -Tag 'InvokeQuery' { + BeforeAll { + $mockExpectedQuery = '' + + $mockConnectSql = { + return @( + ( + New-Object -TypeName PSObject -Property @{ + Databases = @{ + 'master' = ( + New-Object -TypeName PSObject -Property @{ Name = 'master' } | + Add-Member -MemberType ScriptMethod -Name ExecuteNonQuery -Value { + param + ( + [Parameter()] + [System.String] + $sqlCommand + ) + + if ( $sqlCommand -ne $mockExpectedQuery ) + { + throw + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name ExecuteWithResults -Value { + param + ( + [Parameter()] + [System.String] + $sqlCommand + ) + + if ( $sqlCommand -ne $mockExpectedQuery ) + { + throw + } + + return New-Object -TypeName System.Data.DataSet + } -PassThru + ) + } + } + ) + ) } - It 'Should import the SqlServer module without throwing' { - Mock -CommandName Get-Module -MockWith $mockGetModuleSqlServer -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + $mockThrowLocalizedMessage = { + throw $Message + } + } - { Import-SQLPSModule } | Should -Not -Throw + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -ModuleName $script:dscResourceName -Verifiable + Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable + } - Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It - } + $queryParams = @{ + SQLServer = 'Server1' + SQLInstanceName = 'MSSQLSERVER' + Database = 'master' + Query = '' } - Context 'When only module SQLPS exists, but not loaded into the session, and using -Force' { - BeforeAll { - Mock -CommandName Remove-Module - Mock -CommandName Get-Module -ParameterFilter { - $PSBoundParameters.ContainsKey('Name') -eq $true - } + Context 'Execute a query with no results' { + It 'Should execute the query silently' { + $queryParams.Query = "EXEC sp_configure 'show advanced option', '1'" + $mockExpectedQuery = $queryParams.Query.Clone() - $mockExpectedModuleNameToImport = $sqlPsExpectedModulePath + { Invoke-Query @queryParams } | Should -Not -Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - It 'Should import the SqlServer module without throwing' { - Mock -CommandName Get-Module -MockWith $mockGetModuleSqlPs -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Verifiable - Mock -CommandName Get-Module -MockWith { - return $null - } -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + It 'Should throw the correct error, ExecuteNonQueryFailed, when executing the query fails' { + $queryParams.Query = 'BadQuery' - { Import-SQLPSModule -Force } | Should -Not -Throw + { Invoke-Query @queryParams } | Should -Throw ($script:localizedData.ExecuteNonQueryFailed -f $queryParams.Database) - Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Remove-Module -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } } - Context 'When neither SqlServer or SQLPS exists' { - $mockExpectedModuleNameToImport = $sqlPsExpectedModulePath - - It 'Should throw the correct error message' { - Mock -CommandName Get-Module + Context 'Execute a query with results' { + It 'Should execute the query and return a result set' { + $queryParams.Query = 'SELECT name FROM sys.databases' + $mockExpectedQuery = $queryParams.Query.Clone() - { Import-SQLPSModule } | Should -Throw $script:localizedData.PowerShellSqlModuleNotFound + Invoke-Query @queryParams -WithResults | Should -Not -BeNullOrEmpty - Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Push-Location -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Pop-Location -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - } - Context 'When Import-Module fails to load the module' { - $mockExpectedModuleNameToImport = 'SqlServer' - - It 'Should throw the correct error message' { - $errorMessage = 'Mock Import-Module throwing a mocked error.' - Mock -CommandName Get-Module -MockWith $mockGetModuleSqlServer -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable - Mock -CommandName Import-Module -MockWith { - throw $errorMessage - } + It 'Should throw the correct error, ExecuteQueryWithResultsFailed, when executing the query fails' { + $queryParams.Query = 'BadQuery' - { Import-SQLPSModule } | Should -Throw ($script:localizedData.FailedToImportPowerShellSqlModule -f $mockExpectedModuleNameToImport) + { Invoke-Query @queryParams -WithResults } | Should -Throw ($script:localizedData.ExecuteQueryWithResultsFailed -f $queryParams.Database) - Assert-MockCalled -CommandName Get-Module -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } } + } - # This is to test the tests (so the mock throws correctly) - Context 'When mock Import-Module is called with wrong module name' { - $mockExpectedModuleNameToImport = 'UnknownModule' + Describe 'DscResource.Common\Update-AvailabilityGroupReplica' -Tag 'UpdateAvailabilityGroupReplica' { + Context 'When the Availability Group Replica is altered' { + It 'Should silently alter the Availability Group Replica' { + $availabilityReplica = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica - It 'Should throw the correct error message' { - Mock -CommandName Get-Module -MockWith $mockGetModuleSqlServer -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should -Not -Throw - { Import-SQLPSModule } | Should -Throw ($script:localizedData.FailedToImportPowerShellSqlModule -f 'SqlServer') + } - Assert-MockCalled -CommandName Get-Module -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + It 'Should throw the correct error, AlterAvailabilityGroupReplicaFailed, when altering the Availability Group Replica fails' { + $availabilityReplica = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $availabilityReplica.Name = 'AlterFailed' + + { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should -Throw ($script:localizedData.AlterAvailabilityGroupReplicaFailed -f $availabilityReplica.Name) } } - - Assert-VerifiableMock } - $mockGetItemProperty_MicrosoftSQLServer_InstanceNames_SQL = { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name $mockInstanceName -Value $mockInstance_InstanceId -PassThru -Force - ) + Describe 'DscResource.Common\Test-LoginEffectivePermissions' -Tag 'TestLoginEffectivePermissions' { + + $mockAllServerPermissionsPresent = @( + 'Connect SQL', + 'Alter Any Availability Group', + 'View Server State' ) - } - $mockGetItemProperty_MicrosoftSQLServer_FullInstanceId_Setup = { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'Version' -Value "$($mockSqlMajorVersion).0.4001.0" -PassThru -Force - ) + $mockServerPermissionsMissing = @( + 'Connect SQL', + 'View Server State' ) - } - $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL = { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' - } + $mockAllLoginPermissionsPresent = @( + 'View Definition', + 'Impersonate' + ) - $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup = { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockInstance_InstanceId\Setup" - } + $mockLoginPermissionsMissing = @( + 'View Definition' + ) - Describe 'Testing Get-SqlInstanceMajorVersion' -Tag GetSqlInstanceMajorVersion { - BeforeEach { - Mock -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL ` - -MockWith $mockGetItemProperty_MicrosoftSQLServer_InstanceNames_SQL ` - -Verifiable + $mockInvokeQueryPermissionsSet = @() # Will be set dynamically in the check - Mock -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup ` - -MockWith $mockGetItemProperty_MicrosoftSQLServer_FullInstanceId_Setup ` - -Verifiable + $mockInvokeQueryPermissionsResult = { + return New-Object -TypeName PSObject -Property @{ + Tables = @{ + Rows = @{ + permission_name = $mockInvokeQueryPermissionsSet + } + } + } } - $mockInstance_InstanceId = "MSSQL$($mockSqlMajorVersion).$($mockInstanceName)" - - Context 'When calling Get-SqlInstanceMajorVersion' { - It 'Should return the correct major SQL version number' { - $result = Get-SqlInstanceMajorVersion -SQLInstanceName $mockInstanceName - $result | Should -Be $mockSqlMajorVersion - - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL + $testLoginEffectiveServerPermissionsParams = @{ + SQLServer = 'Server1' + SQLInstanceName = 'MSSQLSERVER' + Login = 'NT SERVICE\ClusSvc' + Permissions = @() + } - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup - } + $testLoginEffectiveLoginPermissionsParams = @{ + SQLServer = 'Server1' + SQLInstanceName = 'MSSQLSERVER' + Login = 'NT SERVICE\ClusSvc' + Permissions = @() + SecurableClass = 'LOGIN' + SecurableName = 'Login1' } - Context 'When calling Get-SqlInstanceMajorVersion and nothing is returned' { - It 'Should throw the correct error' { - Mock -CommandName Get-ItemProperty ` - -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup ` - -MockWith { - return New-Object -TypeName Object - } -Verifiable + BeforeEach { + Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryPermissionsResult -Verifiable + } - $mockCorrectErrorMessage = ($script:localizedData.SqlServerVersionIsInvalid -f $mockInstanceName) - { Get-SqlInstanceMajorVersion -SQLInstanceName $mockInstanceName } | Should -Throw $mockCorrectErrorMessage + Context 'When all of the permissions are present' { + It 'Should return $true when the desired server permissions are present' { + $mockInvokeQueryPermissionsSet = $mockAllServerPermissionsPresent.Clone() + $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL + Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $true - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly } - } - Assert-VerifiableMock - } + It 'Should return $true when the desired login permissions are present' { + $mockInvokeQueryPermissionsSet = $mockAllLoginPermissionsPresent.Clone() + $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() - Describe 'Testing Get-PrimaryReplicaServerObject' { - BeforeEach { - $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server - $mockServerObject.DomainInstanceName = 'Server1' + Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $true - $mockAvailabilityGroup = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup - $mockAvailabilityGroup.PrimaryReplicaServerName = 'Server1' + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + } } - $mockConnectSql = { - Param - ( - [Parameter()] - [System.String] - $ServerName, + Context 'When a permission is missing' { + It 'Should return $false when the desired server permissions are not present' { + $mockInvokeQueryPermissionsSet = $mockServerPermissionsMissing.Clone() + $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() - [Parameter()] - [System.String] - $InstanceName - ) + Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $false - $mock = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'DomainInstanceName' -Value $ServerName -PassThru - ) - ) + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + } - # Type the mock as a server object - $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') + It 'Should return $false when the specified login has no server permissions assigned' { + $mockInvokeQueryPermissionsSet = @() + $testLoginEffectiveServerPermissionsParams.Permissions = $mockAllServerPermissionsPresent.Clone() - return $mock - } + Test-LoginEffectivePermissions @testLoginEffectiveServerPermissionsParams | Should -Be $false - Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + } - Context 'When the supplied server object is the primary replica' { - It 'Should return the same server object that was supplied' { - $result = Get-PrimaryReplicaServerObject -ServerObject $mockServerObject -AvailabilityGroup $mockAvailabilityGroup + It 'Should return $false when the desired login permissions are not present' { + $mockInvokeQueryPermissionsSet = $mockLoginPermissionsMissing.Clone() + $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() - $result.DomainInstanceName | Should -Be $mockServerObject.DomainInstanceName - $result.DomainInstanceName | Should -Be $mockAvailabilityGroup.PrimaryReplicaServerName + Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $false - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly } - It 'Should return the same server object that was supplied when the PrimaryReplicaServerNameProperty is empty' { - $mockAvailabilityGroup.PrimaryReplicaServerName = '' - - $result = Get-PrimaryReplicaServerObject -ServerObject $mockServerObject -AvailabilityGroup $mockAvailabilityGroup + It 'Should return $false when the specified login has no login permissions assigned' { + $mockInvokeQueryPermissionsSet = @() + $testLoginEffectiveLoginPermissionsParams.Permissions = $mockAllLoginPermissionsPresent.Clone() - $result.DomainInstanceName | Should -Be $mockServerObject.DomainInstanceName - $result.DomainInstanceName | Should -Not -Be $mockAvailabilityGroup.PrimaryReplicaServerName + Test-LoginEffectivePermissions @testLoginEffectiveLoginPermissionsParams | Should -Be $false - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly } } + } - Context 'When the supplied server object is not the primary replica' { - It 'Should the server object of the primary replica' { - $mockAvailabilityGroup.PrimaryReplicaServerName = 'Server2' + Describe 'DscResource.Common\Import-SQLPSModule' -Tag 'ImportSQLPSModule' { + BeforeAll { + <# + This is the path to the latest version of SQLPS, to test that only the + newest SQLPS module is returned. + #> + $sqlPsLatestModulePath = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - $result = Get-PrimaryReplicaServerObject -ServerObject $mockServerObject -AvailabilityGroup $mockAvailabilityGroup + <# + For SQLPS module this should be the root of the module. + The .psd1 file is parsed from the module full path in the code. + #> + $sqlPsExpectedModulePath = Split-Path -Path $sqlPsLatestModulePath -Parent - $result.DomainInstanceName | Should -Not -Be $mockServerObject.DomainInstanceName - $result.DomainInstanceName | Should -Be $mockAvailabilityGroup.PrimaryReplicaServerName - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } - } - } + $mockImportModule = { + if ($Name -ne $mockExpectedModuleNameToImport) + { + throw ('Wrong module was loaded. Expected {0}, but was {1}.' -f $mockExpectedModuleNameToImport, $Name[0]) + } - Describe 'Testing Test-AvailabilityReplicaSeedingModeAutomatic' { + switch ($Name) + { + 'SqlServer' + { + $importModuleResult = @{ + ModuleType = 'Script' + Version = '21.0.17279' + Name = $Name + } + } - BeforeEach { - $mockSqlVersion = 13 - $mockConnectSql = { - Param - ( - [Parameter()] - [System.String] - $ServerName, + $sqlPsExpectedModulePath + { + # Can not use $Name because that contain the path to the module manifest. + $importModuleResult = @( + @{ + ModuleType = 'Script' + Version = '0.0' + # Intentionally formatted to correctly mimic a real run. + Name = 'Sqlps' + Path = $sqlPsLatestModulePath + } + @{ + ModuleType = 'Manifest' + Version = '1.0' + # Intentionally formatted to correctly mimic a real run. + Name = 'sqlps' + Path = $sqlPsLatestModulePath + } + ) + } + } - [Parameter()] - [System.String] - $InstanceName - ) + return $importModuleResult + } - $mock = @( - ( - New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'Version' -Value $mockSqlVersion -PassThru - ) + $mockGetModuleSqlServer = { + # Return an array to test so that the latest version is only imported. + return @( + New-Object -TypeName PSObject -Property @{ + Name = 'SqlServer' + Version = [Version] '1.0' + } + + New-Object -TypeName PSObject -Property @{ + Name = 'SqlServer' + Version = [Version] '2.0' + } ) + } - # Type the mock as a server object - $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') + $mockGetModuleSqlPs = { + # Return an array to test so that the latest version is only imported. + return @( + New-Object -TypeName PSObject -Property @{ + Name = 'SQLPS' + # This is a path to an older version of SQL PS than $sqlPsLatestModulePath. + Path = 'C:\Program Files (x86)\Microsoft SQL Server\120\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } - return $mock + New-Object -TypeName PSObject -Property @{ + Name = 'SQLPS' + Path = $sqlPsLatestModulePath + } + ) } - $mockSeedingMode = 'Manual' - $mockInvokeQuery = { - return @{ - Tables = @{ - Rows = @{ - seeding_mode_desc = $mockSeedingMode - } - } - } + $mockGetModule_SqlServer_ParameterFilter = { + $FullyQualifiedName.Name -eq 'SqlServer' -and $ListAvailable -eq $true } - Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - Mock -CommandName Invoke-Query -MockWith $mockInvokeQuery -Verifiable + $mockGetModule_SQLPS_ParameterFilter = { + $FullyQualifiedName.Name -eq 'SQLPS' -and $ListAvailable -eq $true + } + + $mockThrowLocalizedMessage = { + throw $Message + } } - $testAvailabilityReplicaSeedingModeAutomaticParams = @{ - SQLServer = 'Server1' - SQLInstanceName = 'MSSQLSERVER' - AvailabilityGroupName = 'Group1' - AvailabilityReplicaName = 'Replica2' + BeforeEach { + Mock -CommandName Push-Location -Verifiable + Mock -CommandName Pop-Location -Verifiable + Mock -CommandName Import-Module -MockWith $mockImportModule -Verifiable + Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable } - Context 'When the replica seeding mode is manual' { - # Test SQL 2012 and 2014. Not testing earlier versions because Availability Groups were introduced in SQL 2012. - foreach ( $instanceVersion in @(11,12) ) - { - It ( 'Should return $false when the instance version is {0}' -f $instanceVersion ) { - $mockSqlVersion = $instanceVersion + Context 'When module SqlServer is already loaded into the session' { + BeforeAll { + Mock -CommandName Get-Module -MockWith { + return @{ + Name = 'SqlServer' + } + } + } + + It 'Should use the already loaded module and not call Import-Module' { + { Import-SQLPSModule } | Should -Not -Throw - Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $false + Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It + } + } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly + Context 'When module SQLPS is already loaded into the session' { + BeforeAll { + Mock -CommandName Get-Module -MockWith { + return @{ + Name = 'SQLPS' + } } } - # Test SQL 2016 and later - foreach ( $instanceVersion in @(13,14) ) - { - It ( 'Should return $false when the instance version is {0} and the replica seeding mode is manual' -f $instanceVersion ) { - $mockSqlVersion = $instanceVersion + It 'Should use the already loaded module and not call Import-Module' { + { Import-SQLPSModule } | Should -Not -Throw - Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $false + Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It + } + } - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + Context 'When module SqlServer exists, but not loaded into the session' { + BeforeAll { + Mock -CommandName Get-Module -ParameterFilter { + $PSBoundParameters.ContainsKey('Name') -eq $true } + + $mockExpectedModuleNameToImport = 'SqlServer' } - } - Context 'When the replica seeding mode is automatic' { - # Test SQL 2016 and later - foreach ( $instanceVersion in @(13,14) ) - { - It ( 'Should return $true when the instance version is {0} and the replica seeding mode is automatic' -f $instanceVersion ) { - $mockSqlVersion = $instanceVersion - $mockSeedingMode = 'Automatic' + It 'Should import the SqlServer module without throwing' { + Mock -CommandName Get-Module -MockWith $mockGetModuleSqlServer -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable - Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $true + { Import-SQLPSModule } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It } } - } - $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter = { - $Permissions -eq @('IMPERSONATE ANY LOGIN') - } + Context 'When only module SQLPS exists, but not loaded into the session, and using -Force' { + BeforeAll { + Mock -CommandName Remove-Module + Mock -CommandName Get-Module -ParameterFilter { + $PSBoundParameters.ContainsKey('Name') -eq $true + } - $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter = { - $Permissions -eq @('CONTROL SERVER') - } + $mockExpectedModuleNameToImport = $sqlPsExpectedModulePath + } - $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter = { - $Permissions -eq @('IMPERSONATE') - } + It 'Should import the SqlServer module without throwing' { + Mock -CommandName Get-Module -MockWith $mockGetModuleSqlPs -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Verifiable + Mock -CommandName Get-Module -MockWith { + return $null + } -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable - $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter = { - $Permissions -eq @('CONTROL') - } + { Import-SQLPSModule -Force } | Should -Not -Throw - Describe 'Testing Test-ImpersonatePermissions' { - $mockConnectionContextObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection - $mockConnectionContextObject.TrueLogin = 'Login1' + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-Module -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + } + } - $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server - $mockServerObject.ComputerNamePhysicalNetBIOS = 'Server1' - $mockServerObject.ServiceName = 'MSSQLSERVER' - $mockServerObject.ConnectionContext = $mockConnectionContextObject + Context 'When neither SqlServer or SQLPS exists' { + $mockExpectedModuleNameToImport = $sqlPsExpectedModulePath - BeforeEach { - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -MockWith { $false } -Verifiable - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -MockWith { $false } -Verifiable - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -MockWith { $false } -Verifiable - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -MockWith { $false } -Verifiable - } + It 'Should throw the correct error message' { + Mock -CommandName Get-Module - Context 'When impersonate permissions are present for the login' { - It 'Should return true when the impersonate any login permissions are present for the login' { - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -MockWith { $true } -Verifiable - Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $true + { Import-SQLPSModule } | Should -Throw $script:localizedData.PowerShellSqlModuleNotFound - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It } + } - It 'Should return true when the control server permissions are present for the login' { - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -MockWith { $true } -Verifiable - Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $true + Context 'When Import-Module fails to load the module' { + $mockExpectedModuleNameToImport = 'SqlServer' - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly - } + It 'Should throw the correct error message' { + $errorMessage = 'Mock Import-Module throwing a mocked error.' + Mock -CommandName Get-Module -MockWith $mockGetModuleSqlServer -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + Mock -CommandName Import-Module -MockWith { + throw $errorMessage + } - It 'Should return true when the impersonate login permissions are present for the login' { - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -MockWith { $true } -Verifiable - Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $true + { Import-SQLPSModule } | Should -Throw ($script:localizedData.FailedToImportPowerShellSqlModule -f $mockExpectedModuleNameToImport) - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Get-Module -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It } + } - It 'Should return true when the control login permissions are present for the login' { - Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -MockWith { $true } -Verifiable - Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $true + # This is to test the tests (so the mock throws correctly) + Context 'When mock Import-Module is called with wrong module name' { + $mockExpectedModuleNameToImport = 'UnknownModule' - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -Scope It -Times 1 -Exactly + It 'Should throw the correct error message' { + Mock -CommandName Get-Module -MockWith $mockGetModuleSqlServer -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + + { Import-SQLPSModule } | Should -Throw ($script:localizedData.FailedToImportPowerShellSqlModule -f 'SqlServer') + + Assert-MockCalled -CommandName Get-Module -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It } } - Context 'When impersonate permissions are missing for the login' { - It 'Should return false when the server permissions are missing for the login' { - Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $false + Assert-VerifiableMock + } - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -Scope It -Times 0 -Exactly - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -Scope It -Times 0 -Exactly + Describe 'DscResource.Common\Get-SqlInstanceMajorVersion' -Tag 'GetSqlInstanceMajorVersion' { + BeforeAll { + $mockSqlMajorVersion = 13 + $mockInstanceName = 'TEST' + + $mockGetItemProperty_MicrosoftSQLServer_InstanceNames_SQL = { + return @( + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name $mockInstanceName -Value $mockInstance_InstanceId -PassThru -Force + ) + ) } - It 'Should return false when the login permissions are missing for the login' { - Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $false + $mockGetItemProperty_MicrosoftSQLServer_FullInstanceId_Setup = { + return @( + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Version' -Value "$($mockSqlMajorVersion).0.4001.0" -PassThru -Force + ) + ) + } - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -Scope It -Times 1 -Exactly + $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL = { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' + } + + $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockInstance_InstanceId\Setup" } } - } - Describe 'Testing Connect-SQL' -Tag 'ConnectSql' { BeforeEach { - Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable - Mock -CommandName Import-SQLPSModule - Mock -CommandName New-Object ` - -MockWith $mockNewObject_MicrosoftDatabaseEngine ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter ` + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL ` + -MockWith $mockGetItemProperty_MicrosoftSQLServer_InstanceNames_SQL ` + -Verifiable + + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup ` + -MockWith $mockGetItemProperty_MicrosoftSQLServer_FullInstanceId_Setup ` -Verifiable } - Context 'When connecting to the default instance using Windows Authentication' { - It 'Should return the correct service instance' { - $mockExpectedDatabaseEngineServer = 'TestServer' - $mockExpectedDatabaseEngineInstance = 'MSSQLSERVER' + $mockInstance_InstanceId = "MSSQL$($mockSqlMajorVersion).$($mockInstanceName)" - $databaseEngineServerObject = Connect-SQL -ServerName $mockExpectedDatabaseEngineServer - $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly $mockExpectedDatabaseEngineServer + Context 'When calling Get-SqlInstanceMajorVersion' { + It 'Should return the correct major SQL version number' { + $result = Get-SqlInstanceMajorVersion -SQLInstanceName $mockInstanceName + $result | Should -Be $mockSqlMajorVersion - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL + + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup } } - Context 'When connecting to the default instance using SQL Server Authentication' { - It 'Should return the correct service instance' { - $mockExpectedDatabaseEngineServer = 'TestServer' - $mockExpectedDatabaseEngineInstance = 'MSSQLSERVER' - $mockExpectedDatabaseEngineLoginSecure = $false + Context 'When calling Get-SqlInstanceMajorVersion and nothing is returned' { + It 'Should throw the correct error' { + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup ` + -MockWith { + return New-Object -TypeName Object + } -Verifiable - $databaseEngineServerObject = Connect-SQL -ServerName $mockExpectedDatabaseEngineServer -SetupCredential $mockSetupCredential -LoginType 'SqlLogin' - $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $false - $databaseEngineServerObject.ConnectionContext.Login | Should -Be $mockSetupCredentialUserName - $databaseEngineServerObject.ConnectionContext.SecurePassword | Should -Be $mockSetupCredentialSecurePassword - $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly $mockExpectedDatabaseEngineServer + $mockCorrectErrorMessage = ($script:localizedData.SqlServerVersionIsInvalid -f $mockInstanceName) + { Get-SqlInstanceMajorVersion -SQLInstanceName $mockInstanceName } | Should -Throw $mockCorrectErrorMessage - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL + + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup } } - Context 'When connecting to the named instance using Windows Authentication' { - It 'Should return the correct service instance' { - $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME - $mockExpectedDatabaseEngineInstance = $mockInstanceName + Assert-VerifiableMock + } - $databaseEngineServerObject = Connect-SQL -InstanceName $mockExpectedDatabaseEngineInstance - $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" + Describe 'DscResource.Common\Get-PrimaryReplicaServerObject' -Tag 'GetPrimaryReplicaServerObject' { + BeforeEach { + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.DomainInstanceName = 'Server1' - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter - } + $mockAvailabilityGroup = New-Object -TypeName Microsoft.SqlServer.Management.Smo.AvailabilityGroup + $mockAvailabilityGroup.PrimaryReplicaServerName = 'Server1' } - Context 'When connecting to the named instance using SQL Server Authentication' { - It 'Should return the correct service instance' { - $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME - $mockExpectedDatabaseEngineInstance = $mockInstanceName - $mockExpectedDatabaseEngineLoginSecure = $false + $mockConnectSql = { + Param + ( + [Parameter()] + [System.String] + $ServerName, + + [Parameter()] + [System.String] + $InstanceName + ) - $databaseEngineServerObject = Connect-SQL -InstanceName $mockExpectedDatabaseEngineInstance -SetupCredential $mockSetupCredential -LoginType 'SqlLogin' - $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $false - $databaseEngineServerObject.ConnectionContext.Login | Should -Be $mockSetupCredentialUserName - $databaseEngineServerObject.ConnectionContext.SecurePassword | Should -Be $mockSetupCredentialSecurePassword - $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" + $mock = @( + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'DomainInstanceName' -Value $ServerName -PassThru + ) + ) - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter - } + # Type the mock as a server object + $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') + + return $mock } - Context 'When connecting to the named instance using Windows Authentication and different server name' { - It 'Should return the correct service instance' { - $mockExpectedDatabaseEngineServer = 'SERVER' - $mockExpectedDatabaseEngineInstance = $mockInstanceName + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable - $databaseEngineServerObject = Connect-SQL -ServerName $mockExpectedDatabaseEngineServer -InstanceName $mockExpectedDatabaseEngineInstance - $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" + Context 'When the supplied server object is the primary replica' { + It 'Should return the same server object that was supplied' { + $result = Get-PrimaryReplicaServerObject -ServerObject $mockServerObject -AvailabilityGroup $mockAvailabilityGroup - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter + $result.DomainInstanceName | Should -Be $mockServerObject.DomainInstanceName + $result.DomainInstanceName | Should -Be $mockAvailabilityGroup.PrimaryReplicaServerName + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly } - } - Context 'When connecting to the named instance using Windows Authentication impersonation' { - It 'Should return the correct service instance' { - $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME - $mockExpectedDatabaseEngineInstance = $mockInstanceName + It 'Should return the same server object that was supplied when the PrimaryReplicaServerNameProperty is empty' { + $mockAvailabilityGroup.PrimaryReplicaServerName = '' - $testParameters = @{ - ServerName = $mockExpectedDatabaseEngineServer - InstanceName = $mockExpectedDatabaseEngineInstance - SetupCredential = $mockSetupCredential - } + $result = Get-PrimaryReplicaServerObject -ServerObject $mockServerObject -AvailabilityGroup $mockAvailabilityGroup - $databaseEngineServerObject = Connect-SQL @testParameters - $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true - $databaseEngineServerObject.ConnectionContext.ConnectAsUserPassword | Should -BeExactly $mockSetupCredential.GetNetworkCredential().Password - $databaseEngineServerObject.ConnectionContext.ConnectAsUserName | Should -BeExactly $mockSetupCredential.GetNetworkCredential().UserName - $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true + $result.DomainInstanceName | Should -Be $mockServerObject.DomainInstanceName + $result.DomainInstanceName | Should -Not -Be $mockAvailabilityGroup.PrimaryReplicaServerName - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly } } - Context 'When connecting to the default instance using the correct service instance but does not return a correct Database Engine object' { - It 'Should throw the correct error' { - $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME - $mockExpectedDatabaseEngineInstance = $mockInstanceName + Context 'When the supplied server object is not the primary replica' { + It 'Should the server object of the primary replica' { + $mockAvailabilityGroup.PrimaryReplicaServerName = 'Server2' - Mock -CommandName New-Object ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter ` - -Verifiable + $result = Get-PrimaryReplicaServerObject -ServerObject $mockServerObject -AvailabilityGroup $mockAvailabilityGroup - $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToDatabaseEngineInstance -f $mockExpectedDatabaseEngineServer) - { Connect-SQL } | Should -Throw $mockCorrectErrorMessage + $result.DomainInstanceName | Should -Not -Be $mockServerObject.DomainInstanceName + $result.DomainInstanceName | Should -Be $mockAvailabilityGroup.PrimaryReplicaServerName - Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` - -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly } } - - Assert-VerifiableMock } - Describe 'Testing Test-SQLDscParameterState' -Tag TestSQLDscParameterState { - Context -Name 'When passing values' -Fixture { - It 'Should return true for two identical tables' { - $mockDesiredValues = @{ Example = 'test' } + Describe 'DscResource.Common\Test-AvailabilityReplicaSeedingModeAutomatic' -Tag 'TestAvailabilityReplicaSeedingModeAutomatic' { - $testParameters = @{ - CurrentValues = $mockDesiredValues - DesiredValues = $mockDesiredValues - } + BeforeEach { + $mockSqlVersion = 13 + $mockConnectSql = { + Param + ( + [Parameter()] + [System.String] + $ServerName, - Test-SQLDscParameterState @testParameters | Should -Be $true - } + [Parameter()] + [System.String] + $InstanceName + ) - It 'Should return false when a value is different for [System.String]' { - $mockCurrentValues = @{ Example = [System.String]'something' } - $mockDesiredValues = @{ Example = [System.String]'test' } + $mock = @( + ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Version' -Value $mockSqlVersion -PassThru + ) + ) - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + # Type the mock as a server object + $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') - Test-SQLDscParameterState @testParameters | Should -Be $false + return $mock } - It 'Should return false when a value is different for [System.Int32]' { - $mockCurrentValues = @{ Example = [System.Int32]1 } - $mockDesiredValues = @{ Example = [System.Int32]2 } - - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues + $mockSeedingMode = 'Manual' + $mockInvokeQuery = { + return @{ + Tables = @{ + Rows = @{ + seeding_mode_desc = $mockSeedingMode + } + } } - - Test-SQLDscParameterState @testParameters | Should -Be $false } - It 'Should return false when a value is different for [Int16]' { - $mockCurrentValues = @{ Example = [System.Int16]1 } - $mockDesiredValues = @{ Example = [System.Int16]2 } + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + Mock -CommandName Invoke-Query -MockWith $mockInvokeQuery -Verifiable + } - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + $testAvailabilityReplicaSeedingModeAutomaticParams = @{ + SQLServer = 'Server1' + SQLInstanceName = 'MSSQLSERVER' + AvailabilityGroupName = 'Group1' + AvailabilityReplicaName = 'Replica2' + } - Test-SQLDscParameterState @testParameters | Should -Be $false - } + Context 'When the replica seeding mode is manual' { + # Test SQL 2012 and 2014. Not testing earlier versions because Availability Groups were introduced in SQL 2012. + foreach ( $instanceVersion in @(11,12) ) + { + It ( 'Should return $false when the instance version is {0}' -f $instanceVersion ) { + $mockSqlVersion = $instanceVersion - It 'Should return false when a value is different for [UInt16]' { - $mockCurrentValues = @{ Example = [System.UInt16]1 } - $mockDesiredValues = @{ Example = [System.UInt16]2 } + Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $false - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly } - - Test-SQLDscParameterState @testParameters | Should -Be $false } - It 'Should return false when a value is missing' { - $mockCurrentValues = @{ } - $mockDesiredValues = @{ Example = 'test' } + # Test SQL 2016 and later + foreach ( $instanceVersion in @(13,14) ) + { + It ( 'Should return $false when the instance version is {0} and the replica seeding mode is manual' -f $instanceVersion ) { + $mockSqlVersion = $instanceVersion - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $false - Test-SQLDscParameterState @testParameters | Should -Be $false + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + } } + } - It 'Should return true when only a specified value matches, but other non-listed values do not' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } - $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } + Context 'When the replica seeding mode is automatic' { + # Test SQL 2016 and later + foreach ( $instanceVersion in @(13,14) ) + { + It ( 'Should return $true when the instance version is {0} and the replica seeding mode is automatic' -f $instanceVersion ) { + $mockSqlVersion = $instanceVersion + $mockSeedingMode = 'Automatic' - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - ValuesToCheck = @('Example') + Test-AvailabilityReplicaSeedingModeAutomatic @testAvailabilityReplicaSeedingModeAutomaticParams | Should -Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly } + } + } + } - Test-SQLDscParameterState @testParameters | Should -Be $true + Describe 'DscResource.Common\Test-ImpersonatePermissions' -Tag 'TestImpersonatePermissions' { + BeforeAll { + $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter = { + $Permissions -eq @('IMPERSONATE ANY LOGIN') } - It 'Should return false when only specified values do not match, but other non-listed values do ' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } - $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } + $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter = { + $Permissions -eq @('CONTROL SERVER') + } - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - ValuesToCheck = @('SecondExample') - } + $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter = { + $Permissions -eq @('IMPERSONATE') + } - Test-SQLDscParameterState @testParameters | Should -Be $false + $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter = { + $Permissions -eq @('CONTROL') } - It 'Should return false when an empty hash table is used in the current values' { - $mockCurrentValues = @{ } - $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } + $mockConnectionContextObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerConnection + $mockConnectionContextObject.TrueLogin = 'Login1' - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server + $mockServerObject.ComputerNamePhysicalNetBIOS = 'Server1' + $mockServerObject.ServiceName = 'MSSQLSERVER' + $mockServerObject.ConnectionContext = $mockConnectionContextObject + } + + BeforeEach { + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -MockWith { $false } -Verifiable + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -MockWith { $false } -Verifiable + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -MockWith { $false } -Verifiable + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -MockWith { $false } -Verifiable + } + + Context 'When impersonate permissions are present for the login' { + It 'Should return true when the impersonate any login permissions are present for the login' { + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -MockWith { $true } -Verifiable + Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $true - Test-SQLDscParameterState @testParameters | Should -Be $false + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly } - It 'Should return true when evaluating a table against a CimInstance' { - $mockCurrentValues = @{ Handle = '0'; ProcessId = '1000' } + It 'Should return true when the control server permissions are present for the login' { + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -MockWith { $true } -Verifiable + Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $true - $mockWin32ProcessProperties = @{ - Handle = 0 - ProcessId = 1000 - } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly + } - $mockNewCimInstanceParameters = @{ - ClassName = 'Win32_Process' - Property = $mockWin32ProcessProperties - Key = 'Handle' - ClientOnly = $true - } + It 'Should return true when the impersonate login permissions are present for the login' { + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -MockWith { $true } -Verifiable + Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $true - $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -Scope It -Times 1 -Exactly + } - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - ValuesToCheck = @('Handle','ProcessId') - } + It 'Should return true when the control login permissions are present for the login' { + Mock -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -MockWith { $true } -Verifiable + Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $true - Test-SQLDscParameterState @testParameters | Should -Be $true + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -Scope It -Times 1 -Exactly } + } - It 'Should return false when evaluating a table against a CimInstance and a value is wrong' { - $mockCurrentValues = @{ Handle = '1'; ProcessId = '1000' } + Context 'When impersonate permissions are missing for the login' { + It 'Should return false when the server permissions are missing for the login' { + Test-ImpersonatePermissions -ServerObject $mockServerObject | Should -Be $false - $mockWin32ProcessProperties = @{ - Handle = 0 - ProcessId = 1000 - } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -Scope It -Times 0 -Exactly + } - $mockNewCimInstanceParameters = @{ - ClassName = 'Win32_Process' - Property = $mockWin32ProcessProperties - Key = 'Handle' - ClientOnly = $true - } + It 'Should return false when the login permissions are missing for the login' { + Test-ImpersonatePermissions -ServerObject $mockServerObject -SecurableName 'Login1' | Should -Be $false - $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateAnyLogin_ParameterFilter -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlServer_ParameterFilter -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ImpersonateLogin_ParameterFilter -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -ParameterFilter $mockTestLoginEffectivePermissions_ControlLogin_ParameterFilter -Scope It -Times 1 -Exactly + } + } + } - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - ValuesToCheck = @('Handle','ProcessId') + Describe 'DscResource.Common\Connect-SQL' -Tag 'ConnectSql' { + BeforeAll { + $mockNewObject_MicrosoftDatabaseEngine = { + <# + $ArgumentList[0] will contain the ServiceInstance when calling mock New-Object. + But since the mock New-Object will also be called without arguments, we first + have to evaluate if $ArgumentList contains values. + #> + if( $ArgumentList.Count -gt 0) + { + $serverInstance = $ArgumentList[0] } - Test-SQLDscParameterState @testParameters | Should -Be $false - } - - It 'Should return true when evaluating a hash table containing an array' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = @('1','2') } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + return New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name Status -Value { + if ($mockExpectedDatabaseEngineInstance -eq 'MSSQLSERVER') + { + $mockExpectedServiceInstance = $mockExpectedDatabaseEngineServer + } + else + { + $mockExpectedServiceInstance = "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" + } - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + if ( $this.ConnectionContext.ServerInstance -eq $mockExpectedServiceInstance ) + { + return 'Online' + } + else + { + return $null + } + } -PassThru | + Add-Member -MemberType NoteProperty -Name ConnectionContext -Value ( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name ServerInstance -Value $serverInstance -PassThru | + #Add-Member -MemberType ScriptProperty -Name LoginSecure -Value { [System.Boolean] $mockExpectedDatabaseEngineLoginSecure } -PassThru -Force | + Add-Member -MemberType NoteProperty -Name LoginSecure -Value $true -PassThru | + Add-Member -MemberType NoteProperty -Name Login -Value '' -PassThru | + Add-Member -MemberType NoteProperty -Name SecurePassword -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name ConnectAsUser -Value $false -PassThru | + Add-Member -MemberType NoteProperty -Name ConnectAsUserPassword -Value '' -PassThru | + Add-Member -MemberType NoteProperty -Name ConnectAsUserName -Value '' -PassThru | + Add-Member -MemberType ScriptMethod -Name Connect -Value { + if ($mockExpectedDatabaseEngineInstance -eq 'MSSQLSERVER') + { + $mockExpectedServiceInstance = $mockExpectedDatabaseEngineServer + } + else + { + $mockExpectedServiceInstance = "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" + } + + if ($this.serverInstance -ne $mockExpectedServiceInstance) + { + throw ("Mock method Connect() was expecting ServerInstance to be '{0}', but was '{1}'." -f $mockExpectedServiceInstance, $this.serverInstance ) + } + + if ($mockThrowInvalidOperation) + { + throw 'Unable to connect.' + } + } -PassThru -Force + ) -PassThru -Force + } + + $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter = { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Server' + } + + $mockThrowLocalizedMessage = { + throw $Message + } + + $mockSetupCredentialUserName = 'TestUserName12345' + $mockSetupCredentialPassword = 'StrongOne7.' + $mockSetupCredentialSecurePassword = ConvertTo-SecureString -String $mockSetupCredentialPassword -AsPlainText -Force + $mockSetupCredential = New-Object -TypeName PSCredential -ArgumentList ($mockSetupCredentialUserName, $mockSetupCredentialSecurePassword) + } - Test-SQLDscParameterState @testParameters | Should -Be $true - } + BeforeEach { + Mock -CommandName New-InvalidOperationException -MockWith $mockThrowLocalizedMessage -Verifiable + Mock -CommandName Import-SQLPSModule + Mock -CommandName New-Object ` + -MockWith $mockNewObject_MicrosoftDatabaseEngine ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter ` + -Verifiable + } - It 'Should return false when evaluating a hash table containing an array with wrong values' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = @('A','B') } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + Context 'When connecting to the default instance using Windows Authentication' { + It 'Should return the correct service instance' { + $mockExpectedDatabaseEngineServer = 'TestServer' + $mockExpectedDatabaseEngineInstance = 'MSSQLSERVER' - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + $databaseEngineServerObject = Connect-SQL -ServerName $mockExpectedDatabaseEngineServer + $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly $mockExpectedDatabaseEngineServer - Test-SQLDscParameterState @testParameters | Should -Be $false + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter } + } - It 'Should return false when evaluating a hash table containing an array, but the CurrentValues are missing an array' { - $mockCurrentValues = @{ Example = 'test' } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + Context 'When connecting to the default instance using SQL Server Authentication' { + It 'Should return the correct service instance' { + $mockExpectedDatabaseEngineServer = 'TestServer' + $mockExpectedDatabaseEngineInstance = 'MSSQLSERVER' + $mockExpectedDatabaseEngineLoginSecure = $false - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + $databaseEngineServerObject = Connect-SQL -ServerName $mockExpectedDatabaseEngineServer -SetupCredential $mockSetupCredential -LoginType 'SqlLogin' + $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $false + $databaseEngineServerObject.ConnectionContext.Login | Should -Be $mockSetupCredentialUserName + $databaseEngineServerObject.ConnectionContext.SecurePassword | Should -Be $mockSetupCredentialSecurePassword + $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly $mockExpectedDatabaseEngineServer - Test-SQLDscParameterState @testParameters | Should -Be $false + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter } + } - It 'Should return false when evaluating a hash table containing an array, but the property i CurrentValues is $null' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = $null } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1','2') } + Context 'When connecting to the named instance using Windows Authentication' { + It 'Should return the correct service instance' { + $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME + $mockExpectedDatabaseEngineInstance = $mockInstanceName - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + $databaseEngineServerObject = Connect-SQL -InstanceName $mockExpectedDatabaseEngineInstance + $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - Test-SQLDscParameterState @testParameters | Should -Be $false + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter } } - Context -Name 'When passing invalid types for DesiredValues' -Fixture { - It 'Should throw the correct error when DesiredValues is of wrong type' { - $mockCurrentValues = @{ Example = 'something' } - $mockDesiredValues = 'NotHashTable' + Context 'When connecting to the named instance using SQL Server Authentication' { + It 'Should return the correct service instance' { + $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME + $mockExpectedDatabaseEngineInstance = $mockInstanceName + $mockExpectedDatabaseEngineLoginSecure = $false - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } + $databaseEngineServerObject = Connect-SQL -InstanceName $mockExpectedDatabaseEngineInstance -SetupCredential $mockSetupCredential -LoginType 'SqlLogin' + $databaseEngineServerObject.ConnectionContext.LoginSecure | Should -Be $false + $databaseEngineServerObject.ConnectionContext.Login | Should -Be $mockSetupCredentialUserName + $databaseEngineServerObject.ConnectionContext.SecurePassword | Should -Be $mockSetupCredentialSecurePassword + $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - $mockCorrectErrorMessage = ($script:localizedData.PropertyTypeInvalidForDesiredValues -f $testParameters.DesiredValues.GetType().Name) - { Test-SQLDscParameterState @testParameters } | Should -Throw $mockCorrectErrorMessage + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter } + } - It 'Should write a warning when DesiredValues contain an unsupported type' { - Mock -CommandName Write-Warning -Verifiable - - # This is a dummy type to test with a type that could never be a correct one. - class MockUnknownType - { - [ValidateNotNullOrEmpty()] - [System.String] - $Property1 + Context 'When connecting to the named instance using Windows Authentication and different server name' { + It 'Should return the correct service instance' { + $mockExpectedDatabaseEngineServer = 'SERVER' + $mockExpectedDatabaseEngineInstance = $mockInstanceName - [ValidateNotNullOrEmpty()] - [System.String] - $Property2 + $databaseEngineServerObject = Connect-SQL -ServerName $mockExpectedDatabaseEngineServer -InstanceName $mockExpectedDatabaseEngineInstance + $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" - MockUnknownType() - { - } - } + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter + } + } - $mockCurrentValues = @{ Example = New-Object -TypeName MockUnknownType } - $mockDesiredValues = @{ Example = New-Object -TypeName MockUnknownType } + Context 'When connecting to the named instance using Windows Authentication impersonation' { + It 'Should return the correct service instance' { + $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME + $mockExpectedDatabaseEngineInstance = $mockInstanceName $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues + ServerName = $mockExpectedDatabaseEngineServer + InstanceName = $mockExpectedDatabaseEngineInstance + SetupCredential = $mockSetupCredential } - Test-SQLDscParameterState @testParameters | Should -Be $false + $databaseEngineServerObject = Connect-SQL @testParameters + $databaseEngineServerObject.ConnectionContext.ServerInstance | Should -BeExactly "$mockExpectedDatabaseEngineServer\$mockExpectedDatabaseEngineInstance" + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true + $databaseEngineServerObject.ConnectionContext.ConnectAsUserPassword | Should -BeExactly $mockSetupCredential.GetNetworkCredential().Password + $databaseEngineServerObject.ConnectionContext.ConnectAsUserName | Should -BeExactly $mockSetupCredential.GetNetworkCredential().UserName + $databaseEngineServerObject.ConnectionContext.ConnectAsUser | Should -Be $true - Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter } } - Context -Name 'When passing an CimInstance as DesiredValue and ValuesToCheck is $null' -Fixture { + Context 'When connecting to the default instance using the correct service instance but does not return a correct Database Engine object' { It 'Should throw the correct error' { - $mockCurrentValues = @{ Example = 'something' } - - $mockWin32ProcessProperties = @{ - Handle = 0 - ProcessId = 1000 - } - - $mockNewCimInstanceParameters = @{ - ClassName = 'Win32_Process' - Property = $mockWin32ProcessProperties - Key = 'Handle' - ClientOnly = $true - } + $mockExpectedDatabaseEngineServer = $env:COMPUTERNAME + $mockExpectedDatabaseEngineInstance = $mockInstanceName - $mockDesiredValues = New-CimInstance @mockNewCimInstanceParameters + Mock -CommandName New-Object ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter ` + -Verifiable - $testParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - ValuesToCheck = $null - } + $mockCorrectErrorMessage = ($script:localizedData.FailedToConnectToDatabaseEngineInstance -f $mockExpectedDatabaseEngineServer) + { Connect-SQL } | Should -Throw $mockCorrectErrorMessage - $mockCorrectErrorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck - { Test-SQLDscParameterState @testParameters } | Should -Throw $mockCorrectErrorMessage + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftDatabaseEngine_ParameterFilter } } Assert-VerifiableMock } - Describe 'Testing New-WarningMessage' -Tag NewWarningMessage { + Describe 'DscResource.Common\New-WarningMessage' -Tag 'NewWarningMessage' { Context -Name 'When writing a localized warning message' -Fixture { It 'Should write the error message without throwing' { Mock -CommandName Write-Warning -Verifiable @@ -1796,7 +2424,7 @@ InModuleScope $script:helperModuleName { Assert-VerifiableMock } - Describe 'Testing New-TerminatingError' -Tag NewWarningMessage { + Describe 'DscResource.Common\New-TerminatingError' -Tag 'NewTerminatingError' { Context -Name 'When building a localized error message' -Fixture { It 'Should return the correct error record with the correct error message' { $errorRecord = New-TerminatingError -ErrorType 'NoKeyFound' -FormatArgs 'Dummy error' @@ -1810,7 +2438,7 @@ InModuleScope $script:helperModuleName { It 'Should return the correct error record with a matching FullyQualifiedErrorId' { $errorRecord = New-TerminatingError -ErrorType 'NoKeyFound' -FormatArgs 'Dummy error' - $errorRecord.FullyQualifiedErrorId | Should -Be 'SqlServerDSCHelper.NoKeyFound' + $errorRecord.FullyQualifiedErrorId | Should -Be 'DscResource.NoKeyFound' } It 'Should return the correct error record with a matching FullyQualifiedErrorId when there is no calling module' { @@ -1844,7 +2472,7 @@ InModuleScope $script:helperModuleName { Assert-VerifiableMock } - Describe 'Testing Split-FullSQLInstanceName' { + Describe 'DscResource.Common\Split-FullSQLInstanceName' { Context 'When the "FullSQLInstanceName" parameter is not supplied' { It 'Should throw when the "FullSQLInstanceName" parameter is $null' { { Split-FullSQLInstanceName -FullSQLInstanceName $null } | Should -Throw @@ -1874,7 +2502,7 @@ InModuleScope $script:helperModuleName { } } - Describe 'Testing Test-ClusterPermissions' { + Describe 'DscResource.Common\Test-ClusterPermissions' { BeforeAll { Mock -CommandName Test-LoginEffectivePermissions -MockWith { $mockClusterServicePermissionsPresent @@ -1963,21 +2591,24 @@ InModuleScope $script:helperModuleName { } } - $mockGetService = { - return @{ - Name = $mockDynamicServiceName - DisplayName = $mockDynamicServiceDisplayName - DependentServices = @( - @{ - Name = $mockDynamicDependedServiceName - Status = 'Running' - DependentServices = @() + Describe 'DscResource.Common\Restart-ReportingServicesService' -Tag 'RestartReportingServicesService' { + BeforeAll { + $mockGetService = { + return @{ + Name = $mockDynamicServiceName + DisplayName = $mockDynamicServiceDisplayName + DependentServices = @( + @{ + Name = $mockDynamicDependedServiceName + Status = 'Running' + DependentServices = @() + } + ) } - ) + } + } - } - Describe 'Testing Restart-ReportingServicesService' { Context 'When restarting a Report Services default instance' { BeforeAll { $mockServiceName = 'ReportServer' @@ -2025,7 +2656,7 @@ InModuleScope $script:helperModuleName { } -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Stop-Service -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 2 - } + } } Context 'When restarting a Report Services named instance using a wait timer' { @@ -2052,11 +2683,11 @@ InModuleScope $script:helperModuleName { Assert-MockCalled -CommandName Stop-Service -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Start-Service -Scope It -Exactly -Times 2 Assert-MockCalled -CommandName Start-Sleep -Scope It -Exactly -Times 1 - } + } } } - Describe 'Testing Test-ActiveNode' { + Describe 'DscResource.Common\Test-ActiveNode' -Tag 'TestActiveNode' { BeforeAll { $mockServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server @@ -2101,15 +2732,17 @@ InModuleScope $script:helperModuleName { } } - Describe "Invoke-SqlScript" { - $invokeScriptFileParameters = @{ - ServerInstance = $env:COMPUTERNAME - InputFile = "set.sql" - } + Describe 'DscResource.Common\Invoke-SqlScript' -Tag 'InvokeSqlScript' { + BeforeAll { + $invokeScriptFileParameters = @{ + ServerInstance = $env:COMPUTERNAME + InputFile = "set.sql" + } - $invokeScriptQueryParameters = @{ - ServerInstance = $env:COMPUTERNAME - Query = "Test Query" + $invokeScriptQueryParameters = @{ + ServerInstance = $env:COMPUTERNAME + Query = "Test Query" + } } Context 'Invoke-SqlScript fails to import SQLPS module' { @@ -2125,55 +2758,71 @@ InModuleScope $script:helperModuleName { } Context 'Invoke-SqlScript is called with credentials' { - $passwordPlain = "password" - $user = "User" + BeforeAll { + $mockPasswordPlain = 'password' + $mockUsername = 'User' - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -ParameterFilter { - ($Username -eq $user) -and ($Password -eq $passwordPlain) - } + $password = ConvertTo-SecureString -String $mockPasswordPlain -AsPlainText -Force + $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $mockUsername, $password - $password = ConvertTo-SecureString -String $passwordPlain -AsPlainText -Force - $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $password + Mock -CommandName Import-SQLPSModule -MockWith {} + Mock -CommandName Invoke-Sqlcmd -ParameterFilter { + $Username -eq $mockUsername -and $Password -eq $mockPasswordPlain + } + } - It 'Should call Invoke-Sqlcmd with correct File parameterset parameters' { - $invokeScriptFileParameters.Add("Credential", $cred) + It 'Should call Invoke-Sqlcmd with correct File ParameterSet parameters' { + $invokeScriptFileParameters.Add('Credential', $credential) $null = Invoke-SqlScript @invokeScriptFileParameters Assert-MockCalled -CommandName Invoke-Sqlcmd -ParameterFilter { - ($Username -eq $user) -and ($Password -eq $passwordPlain) + $Username -eq $mockUsername -and $Password -eq $mockPasswordPlain } -Times 1 -Exactly -Scope It } - It 'Should call Invoke-Sqlcmd with correct Query parameterset parameters' { - $invokeScriptQueryParameters.Add("Credential", $cred) + It 'Should call Invoke-Sqlcmd with correct Query ParameterSet parameters' { + $invokeScriptQueryParameters.Add('Credential', $credential) $null = Invoke-SqlScript @invokeScriptQueryParameters Assert-MockCalled -CommandName Invoke-Sqlcmd -ParameterFilter { - ($Username -eq $user) -and ($Password -eq $passwordPlain) + $Username -eq $mockUsername -and $Password -eq $mockPasswordPlain } -Times 1 -Exactly -Scope It } } Context 'Invoke-SqlScript fails to execute the SQL scripts' { - $errorMessage = "Failed to run SQL Script" + $errorMessage = 'Failed to run SQL Script' Mock -CommandName Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith { throw $errorMessage } - It 'Should throw the correct error from File parameterset Invoke-Sqlcmd' { + It 'Should throw the correct error from File ParameterSet Invoke-Sqlcmd' { { Invoke-SqlScript @invokeScriptFileParameters } | Should Throw $errorMessage } - It 'Should throw the correct error from Query parameterset Invoke-Sqlcmd' { + It 'Should throw the correct error from Query ParameterSet Invoke-Sqlcmd' { { Invoke-SqlScript @invokeScriptQueryParameters } | Should Throw $errorMessage } } } - Describe 'Testing Get-ServiceAccount'{ + Describe 'DscResource.Common\Get-ServiceAccount' -Tag 'GetServiceAccount' { + BeforeAll { + $mockLocalSystemAccountUserName = 'NT AUTHORITY\SYSTEM' + $mockLocalSystemAccountCredential = New-Object System.Management.Automation.PSCredential $mockLocalSystemAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) + + $mockManagedServiceAccountUserName = 'CONTOSO\msa$' + $mockManagedServiceAccountCredential = New-Object System.Management.Automation.PSCredential $mockManagedServiceAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) + + $mockDomainAccountUserName = 'CONTOSO\User1' + $mockDomainAccountCredential = New-Object System.Management.Automation.PSCredential $mockDomainAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) + + $mockLocalServiceAccountUserName = 'NT SERVICE\MyService' + $mockLocalServiceAccountCredential = New-Object System.Management.Automation.PSCredential $mockLocalServiceAccountUserName, (ConvertTo-SecureString "Password1" -AsPlainText -Force) + } + Context 'When getting service account' { It 'Should return NT AUTHORITY\SYSTEM' { $returnValue = Get-ServiceAccount -ServiceAccount $mockLocalSystemAccountCredential @@ -2204,7 +2853,15 @@ InModuleScope $script:helperModuleName { } } - Describe 'Testing Find-ExceptionByNumber'{ + Describe 'DscResource.Common\Find-ExceptionByNumber'{ + BeforeAll { + $mockInnerException = New-Object System.Exception "This is a mock inner excpetion object" + $mockInnerException | Add-Member -Name 'Number' -Value 2 -MemberType NoteProperty + + $mockException = New-Object System.Exception "This is a mock exception object", $mockInnerException + $mockException | Add-Member -Name 'Number' -Value 1 -MemberType NoteProperty + } + Context 'When searching Exception objects'{ It 'Should return true for main exception' { Find-ExceptionByNumber -ExceptionToSearch $mockException -ErrorNumber 1 | Should -Be $true @@ -2220,3 +2877,4 @@ InModuleScope $script:helperModuleName { } } } + diff --git a/Tests/Unit/DscResource.LocalizationHelper.Tests.ps1 b/Tests/Unit/DscResource.LocalizationHelper.Tests.ps1 new file mode 100644 index 000000000..2d054fab8 --- /dev/null +++ b/Tests/Unit/DscResource.LocalizationHelper.Tests.ps1 @@ -0,0 +1,203 @@ +<# + .SYNOPSIS + Automated unit test for helper functions in module DscResource.LocalizationHelper. + + .NOTES + To run this script locally, please make sure to first run the bootstrap + script. Read more at + https://github.com/PowerShell/SqlServerDsc/blob/dev/CONTRIBUTING.md#bootstrap-script-assert-testenvironment +#> + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (Test-SkipContinuousIntegrationTask -Type 'Unit') +{ + return +} + +# Import the DscResource.LocalizationHelper module to test +$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules\DscResource.LocalizationHelper' + +Import-Module -Name (Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper.psm1') -Force + +InModuleScope 'DscResource.LocalizationHelper' { + Describe 'DscResource.LocalizationHelper\Get-LocalizedData' { + $mockTestPath = { + return $mockTestPathReturnValue + } + + $mockImportLocalizedData = { + $BaseDirectory | Should -Be $mockExpectedLanguagePath + } + + BeforeEach { + Mock -CommandName Test-Path -MockWith $mockTestPath -Verifiable + Mock -CommandName Import-LocalizedData -MockWith $mockImportLocalizedData -Verifiable + } + + Context 'When loading localized data for Swedish' { + $mockExpectedLanguagePath = 'sv-SE' + $mockTestPathReturnValue = $true + + It 'Should call Import-LocalizedData with sv-SE language' { + Mock -CommandName Join-Path -MockWith { + return 'sv-SE' + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + + $mockExpectedLanguagePath = 'en-US' + $mockTestPathReturnValue = $false + + It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' { + Mock -CommandName Join-Path -MockWith { + return $ChildPath + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 4 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + + Context 'When $ScriptRoot is set to a path' { + $mockExpectedLanguagePath = 'sv-SE' + $mockTestPathReturnValue = $true + + It 'Should call Import-LocalizedData with sv-SE language' { + Mock -CommandName Join-Path -MockWith { + return 'sv-SE' + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should -Not -Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + + $mockExpectedLanguagePath = 'en-US' + $mockTestPathReturnValue = $false + + It 'Should call Import-LocalizedData and fallback to en-US if sv-SE language does not exist' { + Mock -CommandName Join-Path -MockWith { + return $ChildPath + } -Verifiable + + { Get-LocalizedData -ResourceName 'DummyResource' -ScriptRoot '.' } | Should -Not -Throw + + Assert-MockCalled -CommandName Join-Path -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Test-Path -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-LocalizedData -Exactly -Times 1 -Scope It + } + } + } + + Context 'When loading localized data for English' { + Mock -CommandName Join-Path -MockWith { + return 'en-US' + } -Verifiable + + $mockExpectedLanguagePath = 'en-US' + $mockTestPathReturnValue = $true + + It 'Should call Import-LocalizedData with en-US language' { + { Get-LocalizedData -ResourceName 'DummyResource' } | Should -Not -Throw + } + } + + Assert-VerifiableMock + } + + Describe 'DscResource.LocalizationHelper\New-InvalidResultException' { + Context 'When calling with Message parameter only' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + + { New-InvalidResultException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage + } + } + + Context 'When calling with both the Message and ErrorRecord parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockExceptionErrorMessage = 'Mocked exception error message' + + $mockException = New-Object -TypeName System.Exception -ArgumentList $mockExceptionErrorMessage + $mockErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $mockException, $null, 'InvalidResult', $null + + { New-InvalidResultException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) + } + } + + Assert-VerifiableMock + } + + Describe 'DscResource.LocalizationHelper\New-ObjectNotFoundException' { + Context 'When calling with Message parameter only' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + + { New-ObjectNotFoundException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage + } + } + + Context 'When calling with both the Message and ErrorRecord parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockExceptionErrorMessage = 'Mocked exception error message' + + $mockException = New-Object -TypeName System.Exception -ArgumentList $mockExceptionErrorMessage + $mockErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $mockException, $null, 'InvalidResult', $null + + { New-ObjectNotFoundException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.Exception: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) + } + } + + Assert-VerifiableMock + } + + Describe 'DscResource.LocalizationHelper\New-InvalidOperationException' { + Context 'When calling with Message parameter only' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + + { New-InvalidOperationException -Message $mockErrorMessage } | Should -Throw $mockErrorMessage + } + } + + Context 'When calling with both the Message and ErrorRecord parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockExceptionErrorMessage = 'Mocked exception error message' + + $mockException = New-Object -TypeName System.Exception -ArgumentList $mockExceptionErrorMessage + $mockErrorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $mockException, $null, 'InvalidResult', $null + + { New-InvalidOperationException -Message $mockErrorMessage -ErrorRecord $mockErrorRecord } | Should -Throw ('System.InvalidOperationException: {0} ---> System.Exception: {1}' -f $mockErrorMessage, $mockExceptionErrorMessage) + } + } + + Assert-VerifiableMock + } + + Describe 'DscResource.LocalizationHelper\New-InvalidArgumentException' { + Context 'When calling with both the Message and ArgumentName parameter' { + It 'Should throw the correct error' { + $mockErrorMessage = 'Mocked error' + $mockArgumentName = 'MockArgument' + + { New-InvalidArgumentException -Message $mockErrorMessage -ArgumentName $mockArgumentName } | Should -Throw ('Parameter name: {0}' -f $mockArgumentName) + } + } + + Assert-VerifiableMock + } +} diff --git a/Tests/Unit/MSFT_SqlAG.Tests.ps1 b/Tests/Unit/MSFT_SqlAG.Tests.ps1 index cc5d839a4..4ab30f8fe 100644 --- a/Tests/Unit/MSFT_SqlAG.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlAG.Tests.ps1 @@ -39,8 +39,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - # Load the SMO stubs - Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') # Load the default SQL Module stub Import-SQLModuleStub @@ -59,7 +59,7 @@ try InModuleScope $script:dscResourceName { # This is relative to the path of the resource module script, not test test script. $script:moduleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent - Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Tests' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'CommonTestHelper.psm1'))) -Force -Global + Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'Tests' -ChildPath (Join-Path -Path 'TestHelpers' -ChildPath 'CommonTestHelper.psm1'))) -Force #region parameter mocks diff --git a/Tests/Unit/MSFT_SqlAGDatabase.Tests.ps1 b/Tests/Unit/MSFT_SqlAGDatabase.Tests.ps1 index 788ff9f74..24037c00c 100644 --- a/Tests/Unit/MSFT_SqlAGDatabase.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlAGDatabase.Tests.ps1 @@ -29,11 +29,6 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR } Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force -Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent | Split-Path -Parent) -ChildPath 'SqlServerDscHelper.psm1') -Scope Global -Force -Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global - -# Loading mocked classes -Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` @@ -44,6 +39,11 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlAGListener.Tests.ps1 b/Tests/Unit/MSFT_SqlAGListener.Tests.ps1 index a796d1e35..8033eae08 100644 --- a/Tests/Unit/MSFT_SqlAGListener.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlAGListener.Tests.ps1 @@ -39,8 +39,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - # Loading stub cmdlets - Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlAGReplica.Tests.ps1 b/Tests/Unit/MSFT_SqlAGReplica.Tests.ps1 index 6233f9b98..df79ed5fd 100644 --- a/Tests/Unit/MSFT_SqlAGReplica.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlAGReplica.Tests.ps1 @@ -28,9 +28,7 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) } -Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force -Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` @@ -41,6 +39,11 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlDatabase.Tests.ps1 b/Tests/Unit/MSFT_SqlDatabase.Tests.ps1 index 2b993f5ae..feac8d33a 100644 --- a/Tests/Unit/MSFT_SqlDatabase.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlDatabase.Tests.ps1 @@ -39,7 +39,6 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - # Loading mocked classes } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlDatabaseDefaultLocation.Tests.ps1 b/Tests/Unit/MSFT_SqlDatabaseDefaultLocation.Tests.ps1 index fb16ce95c..793bac7f5 100644 --- a/Tests/Unit/MSFT_SqlDatabaseDefaultLocation.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlDatabaseDefaultLocation.Tests.ps1 @@ -27,9 +27,7 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests')) } -Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force -Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` @@ -40,6 +38,12 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global + } function Invoke-TestCleanup @@ -58,7 +62,10 @@ try $mockInstanceName = 'MSSQLSERVER' $mockSQLDataPath = 'C:\Program Files\Data\' $mockSqlLogPath = 'C:\Program Files\Log\' + + # Ending backslash is regression test for issue #1307. $mockSqlBackupPath = 'C:\Program Files\Backup\' + $mockSqlAlterDataPath = 'C:\Program Files\' $mockSqlAlterLogPath = 'C:\Program Files\' $mockSqlAlterBackupPath = 'C:\Program Files\' @@ -88,7 +95,8 @@ try Add-Member -MemberType NoteProperty -Name ComputerNamePhysicalNetBIOS -Value $mockServerName -PassThru -Force | Add-Member -MemberType NoteProperty -Name DefaultFile -Value $mockSqlDataPath -PassThru -Force | Add-Member -MemberType NoteProperty -Name DefaultLog -Value $mockSqlLogPath -PassThru -Force | - Add-Member -MemberType NoteProperty -Name BackupDirectory -Value $mockSqlBackupPath -PassThru -Force | + # Ending backslash is removed because of regression test for issue #1307. + Add-Member -MemberType NoteProperty -Name BackupDirectory -Value $mockSqlBackupPath.TrimEnd('\') -PassThru -Force | Add-Member -MemberType ScriptMethod -Name Alter -Value { if ($mockInvalidOperationForAlterMethod) { @@ -97,36 +105,30 @@ try $script:WasMethodAlterCalled = $true } -PassThru -Force } - - $testCases = @( - @{ - Type = 'Data' - Path = $mockSqlDataPath - AlterPath = $mockSqlAlterDataPath - ExpectedAlterPath = $mockExpectedAlterDataPath - InvalidPath = $mockInvalidPathForData - }, - @{ - Type = 'Log' - Path = $mockSqlLogPath - AlterPath = $mockSqlAlterLogPath - ExpectedAlterPath = $mockExpectedAlterLogPath - InvalidPath = $mockInvalidPathForLog - }, - @{ - Type = 'Backup' - Path = $mockSqlBackupPath - AlterPath = $mockSqlAlterBackupPath - ExpectedAlterPath = $mockExpectedAlterBackupPath - InvalidPath = $mockInvalidPathForBackup - } - ) #endregion Describe 'MSFT_SqlDatabaseDefaultLocation\Get-TargetResource' -Tag 'Get' { + BeforeAll { + $testCases = @( + @{ + Type = 'Data' + Path = $mockSqlDataPath + }, + @{ + Type = 'Log' + Path = $mockSqlLogPath + }, + @{ + Type = 'Backup' + # Ending backslash is removed because of regression test for issue #1307. + Path = $mockSqlBackupPath.TrimEnd('\') + } + ) + } + BeforeEach { Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - Mock -CommandName Test-ActiveNode -Mockwith { + Mock -CommandName Test-ActiveNode -MockWith { param ( [PSObject] @@ -158,6 +160,25 @@ try } Describe 'MSFT_SqlDatabaseDefaultLocation\Test-TargetResource' -Tag 'Test' { + BeforeAll { + $testCases = @( + @{ + Type = 'Data' + Path = $mockSqlDataPath + AlterPath = $mockSqlAlterDataPath + }, + @{ + Type = 'Log' + Path = $mockSqlLogPath + AlterPath = $mockSqlAlterLogPath + }, + @{ + Type = 'Backup' + Path = $mockSqlBackupPath + AlterPath = $mockSqlAlterBackupPath + } + ) + } BeforeEach { Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Test-ActiveNode -MockWith { @@ -236,6 +257,32 @@ try } Describe 'MSFT_SqlDatabaseDefaultLocation\Set-TargetResource' -Tag 'Set' { + BeforeAll { + $testCases = @( + @{ + Type = 'Data' + Path = $mockSqlDataPath + AlterPath = $mockSqlAlterDataPath + ExpectedAlterPath = $mockExpectedAlterDataPath + InvalidPath = $mockInvalidPathForData + }, + @{ + Type = 'Log' + Path = $mockSqlLogPath + AlterPath = $mockSqlAlterLogPath + ExpectedAlterPath = $mockExpectedAlterLogPath + InvalidPath = $mockInvalidPathForLog + }, + @{ + Type = 'Backup' + Path = $mockSqlBackupPath + AlterPath = $mockSqlAlterBackupPath + ExpectedAlterPath = $mockExpectedAlterBackupPath + InvalidPath = $mockInvalidPathForBackup + } + ) + } + BeforeEach { Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Restart-SqlService -Verifiable diff --git a/Tests/Unit/MSFT_SqlDatabasePermission.Tests.ps1 b/Tests/Unit/MSFT_SqlDatabasePermission.Tests.ps1 index e81d15864..aba28b934 100644 --- a/Tests/Unit/MSFT_SqlDatabasePermission.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlDatabasePermission.Tests.ps1 @@ -40,7 +40,7 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlDatabaseRole.Tests.ps1 b/Tests/Unit/MSFT_SqlDatabaseRole.Tests.ps1 index ab05b2d2a..56c38c34f 100644 --- a/Tests/Unit/MSFT_SqlDatabaseRole.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlDatabaseRole.Tests.ps1 @@ -40,7 +40,7 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlRS.Tests.ps1 b/Tests/Unit/MSFT_SqlRS.Tests.ps1 index 49b25afe5..c451117ed 100644 --- a/Tests/Unit/MSFT_SqlRS.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlRS.Tests.ps1 @@ -38,7 +38,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Global -Force + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlRSSetup.Tests.ps1 b/Tests/Unit/MSFT_SqlRSSetup.Tests.ps1 new file mode 100644 index 000000000..4e2d3b002 --- /dev/null +++ b/Tests/Unit/MSFT_SqlRSSetup.Tests.ps1 @@ -0,0 +1,958 @@ +<# + .SYNOPSIS + Automated unit test for MSFT_SqlRSSetup DSC resource. + + .NOTES + To run this script locally, please make sure to first run the bootstrap + script. Read more at + https://github.com/PowerShell/SqlServerDsc/blob/dev/CONTRIBUTING.md#bootstrap-script-assert-testenvironment +#> + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (Test-SkipContinuousIntegrationTask -Type 'Unit') +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceName = 'MSFT_SqlRSSetup' + +#region HEADER + +# Unit Test Template Version: 1.2.0 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -TestType Unit + +#endregion HEADER + +function Invoke-TestSetup +{ +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:dscResourceName { + <# + .SYNOPSIS + Used to test arguments passed to Start-SqlSetupProcess while inside and It-block. + + This function must be called inside a Mock, since it depends being run inside an It-block. + + .PARAMETER Argument + A string containing all the arguments separated with space and each argument should start with '/'. + Only the first string in the array is evaluated. + + .PARAMETER ExpectedArgument + A hash table containing all the expected arguments. + #> + function Test-SetupArgument + { + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Argument, + + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $ExpectedArgument + ) + + $argumentHashTable = @{} + + # Break the argument string into a hash table + ($Argument -split ' ?/') | ForEach-Object { + if ($_ -imatch '(\w+)="?([^\/]+)"?') + { + $key = $Matches[1] + $value = ($Matches[2] -replace '" "',' ') -replace '"','' + + $argumentHashTable.Add($key, $value) + } + elseif ($_ -imatch '(\w+)') + { + $key = $Matches[1] + $value = [System.Management.Automation.SwitchParameter] $true + + $argumentHashTable.Add($key, $value) + } + } + + $actualValues = $argumentHashTable.Clone() + + # Limit the output in the console when everything is fine. + if ($actualValues.Count -ne $ExpectedArgument.Count) + { + Write-Warning -Message 'Verified the setup argument count (expected vs actual)' + Write-Warning -Message ('Expected: {0}' -f ($ExpectedArgument.Keys -join ',')) + Write-Warning -Message ('Actual: {0}' -f ($actualValues.Keys -join ',')) + } + + # Start by checking whether we have the same number of parameters + $actualValues.Count | Should -Be $ExpectedArgument.Count ` + -Because ('the expected arguments was: {0}' -f ($ExpectedArgument.Keys -join ',')) + + Write-Verbose -Message 'Verified actual setup argument values against expected setup argument values' -Verbose + + foreach ($argumentKey in $ExpectedArgument.Keys) + { + $argumentKeyName = $actualValues.GetEnumerator() | + Where-Object -FilterScript { + $_.Name -eq $argumentKey + } | Select-Object -ExpandProperty 'Name' + + $argumentValue = $actualValues.$argumentKey + + $argumentKeyName | Should -Be $argumentKey + $argumentValue | Should -Be $ExpectedArgument.$argumentKey + } + } + + $mockInstanceName = 'SSRS' + $mockCurrentVersion = '14.0.6514.11481' + + # Default parameters that are used for the It-blocks. + $mockDefaultParameters = @{ + InstanceName = $mockInstanceName + IAcceptLicenseTerms = 'Yes' + SourcePath = '\\server\share\SQLServerReportingServices.exe' + } + + Describe "MSFT_SqlRSSetup\Get-TargetResource" -Tag 'Get' { + BeforeEach { + $mockGetTargetResourceParameters = $mockDefaultParameters.Clone() + } + + Context 'When the system is in the desired state' { + Context 'When there are no installed Reporting Services' { + BeforeAll { + Mock -CommandName Get-RegistryPropertyValue + } + + It 'Should return $null as the InstanceName' { + $result = Get-TargetResource @mockGetTargetResourceParameters + $result.InstanceName | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-RegistryPropertyValue -Exactly -Times 1 -Scope 'It' + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @mockGetTargetResourceParameters + $result.IAcceptLicenseTerms | Should -Be $mockGetTargetResourceParameters.IAcceptLicenseTerms + $result.SourcePath | Should -Be $mockGetTargetResourceParameters.SourcePath + + Assert-MockCalled -CommandName Get-RegistryPropertyValue -Exactly -Times 1 -Scope 'It' + } + + It 'Should return $null or $false for the rest of the properties' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.Action | Should -BeNullOrEmpty + $getTargetResourceResult.SourceCredential | Should -BeNullOrEmpty + $getTargetResourceResult.ProductKey | Should -BeNullOrEmpty + $getTargetResourceResult.ForceRestart | Should -BeFalse + $getTargetResourceResult.EditionUpgrade | Should -BeFalse + $getTargetResourceResult.Edition | Should -BeNullOrEmpty + $getTargetResourceResult.LogPath | Should -BeNullOrEmpty + $getTargetResourceResult.InstallFolder | Should -BeNullOrEmpty + $getTargetResourceResult.ErrorDumpDirectory | Should -BeNullOrEmpty + $getTargetResourceResult.CurrentVersion | Should -BeNullOrEmpty + $getTargetResourceResult.ServiceName | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-RegistryPropertyValue -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When there is an installed Reporting Services' { + BeforeAll { + $mockGetRegistryPropertyValue_InstanceName = { + <# + Currently only the one instance name of 'SSRS' is supported, + and the same name is currently used for instance id. + #> + return $mockInstanceName + } + + $mockGetRegistryPropertyValue_InstanceName_ParameterFilter = { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS' ` + -and $Name -eq $mockInstanceName + } + + $mockInstallRootDirectory = 'C:\Program Files\Microsoft SQL Server Reporting Services' + $mockGetRegistryPropertyValue_InstallRootDirectory = { + return $mockInstallRootDirectory + } + + $mockGetRegistryPropertyValue_InstallRootDirectory_ParameterFilter = { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\SSRS\Setup' ` + -and $Name -eq 'InstallRootDirectory' + } + + $mockServiceName = 'SQLServerReportingServices' + $mockGetRegistryPropertyValue_ServiceName = { + return $mockServiceName + } + + $mockGetRegistryPropertyValue_ServiceName_ParameterFilter = { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\SSRS\Setup' ` + -and $Name -eq 'ServiceName' + } + + $mockErrorDumpDir = 'C:\Program Files\Microsoft SQL Server Reporting Services\SSRS\LogFiles' + $mockGetRegistryPropertyValue_ErrorDumpDir = { + return $mockErrorDumpDir + } + + $mockGetRegistryPropertyValue_ErrorDumpDir_ParameterFilter = { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\SSRS\CPE' ` + -and $Name -eq 'ErrorDumpDir' + } + + $mockGetPackage_CurrentVersion = { + return @{ + Version = $mockCurrentVersion + } + } + + Mock -CommandName Get-RegistryPropertyValue ` + -MockWith $mockGetRegistryPropertyValue_InstanceName ` + -ParameterFilter $mockGetRegistryPropertyValue_InstanceName_ParameterFilter + + Mock -CommandName Get-RegistryPropertyValue ` + -MockWith $mockGetRegistryPropertyValue_InstallRootDirectory ` + -ParameterFilter $mockGetRegistryPropertyValue_InstallRootDirectory_ParameterFilter + + Mock -CommandName Get-RegistryPropertyValue ` + -MockWith $mockGetRegistryPropertyValue_ServiceName ` + -ParameterFilter $mockGetRegistryPropertyValue_ServiceName_ParameterFilter + + Mock -CommandName Get-RegistryPropertyValue ` + -MockWith $mockGetRegistryPropertyValue_ErrorDumpDir ` + -ParameterFilter $mockGetRegistryPropertyValue_ErrorDumpDir_ParameterFilter + + # This is a workaround for the issue https://github.com/pester/Pester/issues/604. + function Get-Package + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ProviderName + ) + + throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand + } + + Mock -CommandName Get-Package -MockWith $mockGetPackage_CurrentVersion + } + + It 'Should return the correct InstanceName' { + $result = Get-TargetResource @mockGetTargetResourceParameters + $result.InstanceName | Should -Be $mockGetTargetResourceParameters.InstanceName + + Assert-MockCalled -CommandName Get-RegistryPropertyValue ` + -ParameterFilter $mockGetRegistryPropertyValue_InstanceName_ParameterFilter ` + -Exactly -Times 1 -Scope 'It' + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @mockGetTargetResourceParameters + $result.IAcceptLicenseTerms | Should -Be $mockGetTargetResourceParameters.IAcceptLicenseTerms + $result.SourcePath | Should -Be $mockGetTargetResourceParameters.SourcePath + } + + It 'Should return the correct values for the rest of the properties' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.Action | Should -BeNullOrEmpty + $getTargetResourceResult.SourceCredential | Should -BeNullOrEmpty + $getTargetResourceResult.ProductKey | Should -BeNullOrEmpty + $getTargetResourceResult.ForceRestart | Should -BeFalse + $getTargetResourceResult.EditionUpgrade | Should -BeFalse + $getTargetResourceResult.Edition | Should -BeNullOrEmpty + $getTargetResourceResult.LogPath | Should -BeNullOrEmpty + $getTargetResourceResult.InstallFolder | Should -Be $mockInstallRootDirectory + $getTargetResourceResult.ErrorDumpDirectory | Should -Be $mockErrorDumpDir + $getTargetResourceResult.CurrentVersion | Should -Be $mockCurrentVersion + $getTargetResourceResult.ServiceName | Should -Be $mockServiceName + + Assert-MockCalled -CommandName Get-RegistryPropertyValue ` + -ParameterFilter $mockGetRegistryPropertyValue_InstallRootDirectory_ParameterFilter ` + -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Get-RegistryPropertyValue ` + -ParameterFilter $mockGetRegistryPropertyValue_ServiceName_ParameterFilter ` + -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Get-RegistryPropertyValue ` + -ParameterFilter $mockGetRegistryPropertyValue_ErrorDumpDir_ParameterFilter ` + -Exactly -Times 1 -Scope 'It' + + Assert-MockCalled -CommandName Get-Package -Exactly -Times 1 -Scope 'It' + } + + Context 'When there is an installed Reporting Services, but no installed package is found to determine version' { + BeforeEach { + Mock -CommandName Get-Package + Mock -CommandName Write-Warning + } + + It 'Should return the correct values for the rest of the properties' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.CurrentVersion | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 -Scope 'It' + } + } + } + } + } + + Describe "MSFT_SqlRSSetup\Test-TargetResource" -Tag 'Test' { + BeforeEach { + $mockTestTargetResourceParameters = $mockDefaultParameters.Clone() + } + + Context 'When the system is in the desired state' { + Context 'When there are no installed Reporting Services' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $null + } + } + } + + It 'Should return $true' { + $mockTestTargetResourceParameters['Action'] = 'Uninstall' + + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When there is an installed Reporting Services' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = 'SSRS' + CurrentVersion = $mockCurrentVersion + } + } + + Mock -CommandName Get-FileProductVersion -MockWith { + return [System.Version] $mockCurrentVersion + } + } + + It 'Should return $true' { + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Get-FileProductVersion -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When the installed Reporting Services is an older version that the installation media, but parameter VersionUpgrade is not used' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = 'SSRS' + CurrentVersion = $mockCurrentVersion + } + } + + Mock -CommandName Get-FileProductVersion -MockWith { + return [System.Version] '15.1.1.0' + } + } + + It 'Should return $false' { + # This is called without the parameter 'VersionUpgrade'. + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Get-FileProductVersion -Exactly -Times 1 -Scope 'It' + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When there should be no installed Reporting Services' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = 'SSRS' + } + } + } + + It 'Should return $false' { + $mockTestTargetResourceParameters['Action'] = 'Uninstall' + + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When there are no installed Reporting Services' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $null + } + } + + Mock -CommandName Get-FileProductVersion -MockWith { + return [System.Version] $mockCurrentVersion + } + } + + It 'Should return $false' { + $result = Test-TargetResource @mockTestTargetResourceParameters -Verbose + $result | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When the wrong version of Reporting Services is installed, and parameter VersionUpgrade is used' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = 'SSRS' + CurrentVersion = $mockCurrentVersion + } + } + + Mock -CommandName Get-FileProductVersion -MockWith { + return [System.Version] '15.1.1.0' + } + } + + It 'Should return $false' { + $mockTestTargetResourceParameters['VersionUpgrade'] = $true + + $result = Test-TargetResource @mockTestTargetResourceParameters -Verbose + $result | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Get-FileProductVersion -Exactly -Times 1 -Scope 'It' + } + } + } + } + + Describe "MSFT_SqlRSSetup\Set-TargetResource" -Tag 'Set' { + BeforeAll { + $mockProductKey = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' + } + + BeforeEach { + $mockSetTargetResourceParameters = $mockDefaultParameters.Clone() + + # Reset global variable DSCMachineStatus before each test. + $global:DSCMachineStatus = 0 + } + + + Context 'When providing a missing SourcePath' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + + Mock -CommandName Test-Path -MockWith { + return $false + } + } + + It 'Should throw the correct error message' { + $errorMessage = $script:localizedData.SourcePathNotFound -f $mockSetTargetResourceParameters.SourcePath + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw $errorMessage + } + } + + Context 'When providing a correct path in SourcePath, but no executable' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + + Mock -CommandName Test-Path -MockWith { + return $true + } + + Mock -CommandName Get-Item -MockWith { + return @{ + Extension = '' + } + } + } + + It 'Should throw the correct error message' { + $errorMessage = $script:localizedData.SourcePathNotFound -f $mockSetTargetResourceParameters.SourcePath + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw $errorMessage + } + } + + Context 'When providing both the parameters ProductKey and Edition' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + $mockSetTargetResourceParameters['ProductKey'] = $mockProductKey + } + + It 'Should throw the correct error message' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw $script:localizedData.EditionInvalidParameter + } + } + + Context 'When providing neither the parameters ProductKey or Edition' { + It 'Should throw the correct error message' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw $script:localizedData.EditionMissingParameter + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName Test-Path -MockWith { + return $true + } + + Mock -CommandName Get-Item -MockWith { + return @{ + Extension = '.exe' + } + } + } + + Context 'When Reporting Services are installed with the minimum required parameters' { + BeforeEach { + $mockSetTargetResourceParameters['ProductKey'] = $mockProductKey + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + PID = $mockProductKey + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 0 + } + } + + It 'Should call the correct mocks' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When Reporting Services should be uninstalled' { + BeforeEach { + $mockSetTargetResourceParameters['Action'] = 'Uninstall' + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + Uninstall = [System.Management.Automation.SwitchParameter] $true + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 0 + } + } + + It 'Should call the correct mocks' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When Reporting Services are installed with parameter Edition' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + Edition = 'Dev' + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 0 + } + } + + It 'Should call the correct mocks' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When Reporting Services are installed with parameters ProductKey, SuppressRestart, LogPath, EditionUpgrade, and InstallFolder' { + BeforeEach { + $mockSetTargetResourceParameters['ProductKey'] = $mockProductKey + $mockSetTargetResourceParameters['SuppressRestart'] = $true + $mockSetTargetResourceParameters['LogPath'] = 'log.txt' + $mockSetTargetResourceParameters['EditionUpgrade'] = $true + $mockSetTargetResourceParameters['InstallFolder'] = 'C:\Temp' + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + PID = $mockProductKey + NoRestart = [System.Management.Automation.SwitchParameter] $true + Log = 'log.txt' + EditionUpgrade = [System.Management.Automation.SwitchParameter] $true + InstallFolder = 'C:\Temp' + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 0 + } + } + + It 'Should call the correct mocks' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When Reporting Services are installed using parameter SourceCredential' { + BeforeAll { + $mockLocalPath = 'C:\LocalPath' + + $mockShareCredentialUserName = 'COMPANY\SqlAdmin' + $mockShareCredentialPassword = 'dummyPassW0rd' + $mockShareCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @( + $mockShareCredentialUserName, + ($mockShareCredentialPassword | ConvertTo-SecureString -AsPlainText -Force) + ) + + Mock -CommandName Invoke-InstallationMediaCopy -MockWith { + return $mockLocalPath + } + } + + BeforeEach { + $mockSetTargetResourceParameters['ProductKey'] = $mockProductKey + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 0 + } + } + + It 'Should call the correct mocks' { + $mockSetTargetResourceParameters['SourceCredential'] = $mockShareCredential + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Invoke-InstallationMediaCopy -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + # Have to build the correct path (local path + executable). + $FilePath -eq (Join-Path -Path $mockLocalPath -ChildPath (Split-Path -Path $mockSetTargetResourceParameters.SourcePath -Leaf)) + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When the Reporting Services installation is successful with exit code 3010' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + Edition = 'Dev' + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 3010 + } + } + + It 'Should call the correct mocks, and set $global:DSCMachineStatus to 1' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + # Should set the global DSCMachineStatus variable. + $global:DSCMachineStatus | Should -Be 1 + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + } + + Context 'When the Reporting Services installation is successful with exit code 3010, and called with parameter SuppressRestart' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + $mockSetTargetResourceParameters['SuppressRestart'] = $true + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + Edition = 'Dev' + NoRestart = [System.Management.Automation.SwitchParameter] $true + } + } + + It 'Should call the correct mocks' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + + # Should not set the global DSCMachineStatus variable. + $global:DSCMachineStatus | Should -Be 0 + } + } + } + + Context 'When the Reporting Services installation is successful and ForceRestart is used' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + $mockSetTargetResourceParameters['ForceRestart'] = $true + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + Edition = 'Dev' + } + + Mock -CommandName Test-PendingRestart + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 0 + } + } + + It 'Should call the correct mocks, and set $global:DSCMachineStatus to 1' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + # Should set the global DSCMachineStatus variable. + $global:DSCMachineStatus | Should -Be 1 + + Assert-MockCalled -CommandName Test-PendingRestart -Exactly -Times 0 -Scope 'It' + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When the Reporting Services installation is successful, and there are a pending restart' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + Edition = 'Dev' + } + + Mock -CommandName Test-PendingRestart -MockWith { + return $true + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 0 + } + } + + It 'Should call the correct mocks, and set $global:DSCMachineStatus to 1' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Not -Throw + + # Should set the global DSCMachineStatus variable. + $global:DSCMachineStatus | Should -Be 1 + + Assert-MockCalled -CommandName Test-PendingRestart -Exactly -Times 1 -Scope 'It' + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + } + } + + Context 'When the Reporting Services installation fails' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + Edition = 'Dev' + } + + Mock -CommandName Start-SqlSetupProcess -MockWith { + Test-SetupArgument -Argument $ArgumentList -ExpectedArgument $mockStartSqlSetupProcess_ExpectedArgumentList + + return 1 + } + } + + It 'Should throw the correct error message' { + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw $script:localizedData.SetupFailed + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + + # Should not set the global DSCMachineStatus variable. + $global:DSCMachineStatus | Should -Be 0 + } + + Context 'When the Reporting Services installation fails, and called with parameter LogPath' { + BeforeEach { + $mockSetTargetResourceParameters['Edition'] = 'Development' + $mockSetTargetResourceParameters['LogPath'] = 'TestDrive:\' + + $mockStartSqlSetupProcess_ExpectedArgumentList = @{ + Quiet = [System.Management.Automation.SwitchParameter] $true + IAcceptLicenseTerms = [System.Management.Automation.SwitchParameter] $true + Edition = 'Dev' + log = $mockSetTargetResourceParameters.LogPath + } + } + + It 'Should throw the correct error message' { + $errorMessage = $script:localizedData.SetupFailedWithLog -f $mockSetTargetResourceParameters.LogPath + + { Set-TargetResource @mockSetTargetResourceParameters } | Should -Throw $errorMessage + + Assert-MockCalled -CommandName Start-SqlSetupProcess -ParameterFilter { + $FilePath -eq $mockSetTargetResourceParameters.SourcePath + } -Exactly -Times 1 -Scope 'It' + + # Should not set the global DSCMachineStatus variable. + $global:DSCMachineStatus | Should -Be 0 + } + } + } + } + } + + Describe "MSFT_SqlRSSetup\Convert-EditionName" -Tag 'Helper' { + Context 'When converting edition names' { + $testCases = @( + @{ + InputName = 'Development' + OutputName = 'Dev' + } + @{ + InputName = 'Evaluation' + OutputName = 'Eval' + } + @{ + InputName = 'ExpressAdvanced' + OutputName = 'ExprAdv' + } + @{ + InputName = 'Dev' + OutputName = 'Development' + } + @{ + InputName = 'Eval' + OutputName = 'Evaluation' + } + @{ + InputName = 'ExprAdv' + OutputName = 'ExpressAdvanced' + } + ) + + It 'Should return the value when converting from value ' -TestCases $testCases { + param + ( + [Parameter()] + [System.String] + $InputName, + + [Parameter()] + [System.String] + $OutputName + ) + + Convert-EditionName -Name $InputName | Should -Be $OutputName + } + } + } + + Describe "MSFT_SqlRSSetup\Get-FileProductVersion" -Tag 'Helper' { + Context 'When converting edition names' { + $mockProductVersion = '14.0.0.0' + + BeforeAll { + Mock -CommandName Get-Item -MockWith { + return @{ + VersionInfo = @{ + ProductVersion = $mockProductVersion + } + } + } + } + + It 'Should return the correct product version' { + Get-FileProductVersion -Path 'TestDrive:\MockExecutable.exe' | Should -Be $mockProductVersion + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} + diff --git a/Tests/Unit/MSFT_SqlScript.Tests.ps1 b/Tests/Unit/MSFT_SqlScript.Tests.ps1 index ebc6dced2..cb560a952 100644 --- a/Tests/Unit/MSFT_SqlScript.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlScript.Tests.ps1 @@ -33,8 +33,6 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR } Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'SqlServerDscHelper.psm1') - $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` @@ -44,8 +42,8 @@ $TestEnvironment = Initialize-TestEnvironment ` #endregion HEADER function Invoke-TestSetup { - Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs') - Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Global -Force + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs') } function Invoke-TestCleanup { @@ -59,192 +57,143 @@ try Invoke-TestSetup InModuleScope $script:dscResourceName { - InModuleScope 'SqlServerDscHelper' { - $script:dscModuleName = 'SqlServerDsc' - $resourceName = 'MSFT_SqlScript' - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } + Describe 'MSFT_SqlScript\Get-TargetResource' { + BeforeAll { + $testParameters = @{ + ServerInstance = $env:COMPUTERNAME + SetFilePath = "set.sql" + GetFilePath = "get.sql" + TestFilePath = "test.sql" + } - $testParametersTimeout = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set-timeout.sql" - GetFilePath = "get-timeout.sql" - TestFilePath = "test-timeout.sql" - QueryTimeout = 30 + $testParametersTimeout = @{ + ServerInstance = $env:COMPUTERNAME + SetFilePath = "set-timeout.sql" + GetFilePath = "get-timeout.sql" + TestFilePath = "test-timeout.sql" + QueryTimeout = 30 + } } - Describe "$resourceName\Get-TargetResource" { - - Context 'Get-TargetResource fails to import SQLPS module' { - $throwMessage = "Failed to import SQLPS module." + Context 'When Get-TargetResource returns script results successfully' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' + } - Mock -CommandName Import-SQLPSModule -MockWith { - throw $throwMessage - } + It 'Should return the expected results' { + $result = Get-TargetResource @testParameters - It 'Should throw the correct error from Import-Module' { - { Get-TargetResource @testParameters } | Should -Throw $throwMessage - } + $result.ServerInstance | Should -Be $testParameters.ServerInstance + $result.SetFilePath | Should -Be $testParameters.SetFilePath + $result.GetFilePath | Should -Be $testParameters.GetFilePath + $result.TestFilePath | Should -Be $testParameters.TestFilePath + $result | Should -BeOfType [System.Collections.Hashtable] } + } - Context 'Get-TargetResource returns script results successfully' { - Mock -CommandName Import-SQLPSModule - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } - - It 'Should return the expected results' { - $result = Get-TargetResource @testParameters - - $result.ServerInstance | Should -Be $testParameters.ServerInstance - $result.SetFilePath | Should -Be $testParameters.SetFilePath - $result.GetFilePath | Should -Be $testParameters.GetFilePath - $result.TestFilePath | Should -Be $testParameters.TestFilePath - $result | Should -BeOfType Hashtable - } + Context 'When Get-TargetResource returns script results successfully with query timeout' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' } - Context 'Get-TargetResource returns script results successfully with query timeout' { - Mock -CommandName Import-SQLPSModule - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } - - It 'Should return the expected results' { - $result = Get-TargetResource @testParametersTimeout - $result.ServerInstance | Should -Be $testParametersTimeout.ServerInstance - $result.SetFilePath | Should -Be $testParametersTimeout.SetFilePath - $result.GetFilePath | Should -Be $testParametersTimeout.GetFilePath - $result.TestFilePath | Should -Be $testParametersTimeout.TestFilePath - $result | Should -BeOfType Hashtable - } + It 'Should return the expected results' { + $result = Get-TargetResource @testParametersTimeout + $result.ServerInstance | Should -Be $testParametersTimeout.ServerInstance + $result.SetFilePath | Should -Be $testParametersTimeout.SetFilePath + $result.GetFilePath | Should -Be $testParametersTimeout.GetFilePath + $result.TestFilePath | Should -Be $testParametersTimeout.TestFilePath + $result | Should -BeOfType [System.Collections.Hashtable] } + } - Context 'Get-TargetResource throws an error when running the script in the GetFilePath parameter' { - $errorMessage = "Failed to run SQL Script" + Context 'When Get-TargetResource throws an error when running the script in the GetFilePath parameter' { + $errorMessage = "Failed to run SQL Script" - Mock -CommandName Import-SQLPSModule - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw $errorMessage - } + Mock -CommandName Invoke-SqlScript -MockWith { + throw $errorMessage + } - It 'Should throw the correct error from Invoke-Sqlcmd' { - { Get-TargetResource @testParameters } | Should -Throw $errorMessage - } + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Get-TargetResource @testParameters } | Should -Throw $errorMessage } } + } - Describe "$resourceName\Set-TargetResource" { - - Context 'Set-TargetResource fails to import SQLPS module' { - $throwMessage = "Failed to import SQLPS module." - - Mock -CommandName Import-SQLPSModule -MockWith { throw $throwMessage } - - It 'Should throw the correct error from Import-Module' { - { Set-TargetResource @testParameters } | Should -Throw $throwMessage - } + Describe 'MSFT_SqlScript\Set-TargetResource' { + Context 'When Set-TargetResource runs script without issue' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' } - Context 'Set-TargetResource runs script without issue' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } - - It 'Should return the expected results' { - $result = Set-TargetResource @testParameters - $result | Should -Be '' - } + It 'Should return the expected results' { + $result = Set-TargetResource @testParameters + $result | Should -Be '' } + } - Context 'Set-TargetResource runs script without issue using timeout' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } - - It 'Should return the expected results' { - $result = Set-TargetResource @testParametersTimeout - $result | Should -Be '' - } + Context 'When Set-TargetResource runs script without issue using timeout' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' } - Context 'Set-TargetResource throws an error when running the script in the SetFilePath parameter' { - $errorMessage = "Failed to run SQL Script" - - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw $errorMessage - } - - It 'Should throw the correct error from Invoke-Sqlcmd' { - { Set-TargetResource @testParameters } | Should -Throw $errorMessage - } + It 'Should return the expected results' { + $result = Set-TargetResource @testParametersTimeout + $result | Should -Be '' } } - Describe "$resourceName\Test-TargetResource" { - Context 'Test-TargetResource fails to import SQLPS module' { - $throwMessage = 'Failed to import SQLPS module.' + Context 'When Set-TargetResource throws an error when running the script in the SetFilePath parameter' { + $errorMessage = "Failed to run SQL Script" - Mock -CommandName Import-SQLPSModule -MockWith { - throw $throwMessage - } + Mock -CommandName Invoke-SqlScript -MockWith { + throw $errorMessage + } - It 'Should throw the correct error from Import-Module' { - { Set-TargetResource @testParameters } | Should -Throw $throwMessage - } + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Set-TargetResource @testParameters } | Should -Throw $errorMessage } + } + } - Context 'Test-TargetResource runs script without issue' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith {} + Describe 'MSFT_SqlScript\Test-TargetResource' { + Context 'When Test-TargetResource runs script without issue' { + Mock -CommandName Invoke-SqlScript - It 'Should return true' { - $result = Test-TargetResource @testParameters - $result | Should -Be $true - } + It 'Should return true' { + $result = Test-TargetResource @testParameters + $result | Should -Be $true } + } - Context 'Test-TargetResource runs script without issue with timeout' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith {} + Context 'When Test-TargetResource runs script without issue with timeout' { + Mock -CommandName Invoke-SqlScript - It 'Should return true' { - $result = Test-TargetResource @testParametersTimeout - $result | Should -Be $true - } + It 'Should return true' { + $result = Test-TargetResource @testParametersTimeout + $result | Should -Be $true } + } - Context 'Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw New-Object -TypeName Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException - } + Context 'When Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' { + Mock -CommandName Invoke-SqlScript -MockWith { + throw New-Object -TypeName Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException + } - It 'Should return false' { - $result = Test-TargetResource @testParameters - $result | Should -Be $false - } + It 'Should return false' { + $result = Test-TargetResource @testParameters + $result | Should -Be $false } + } - Context 'Test-TargetResource throws an unexpected error when running the script in the TestFilePath parameter' { - $errorMessage = "Failed to run SQL Script" + Context 'When Test-TargetResource throws an unexpected error when running the script in the TestFilePath parameter' { + $errorMessage = "Failed to run SQL Script" - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw $errorMessage - } + Mock -CommandName Invoke-SqlScript -MockWith { + throw $errorMessage + } - It 'Should throw the correct error from Invoke-Sqlcmd' { - { Test-TargetResource @testParameters } | Should -Throw $errorMessage - } + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Test-TargetResource @testParameters } | Should -Throw $errorMessage } } } diff --git a/Tests/Unit/MSFT_SqlScriptQuery.Tests.ps1 b/Tests/Unit/MSFT_SqlScriptQuery.Tests.ps1 index d8b49d689..9eeb4d83f 100644 --- a/Tests/Unit/MSFT_SqlScriptQuery.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlScriptQuery.Tests.ps1 @@ -33,7 +33,6 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR } Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'SqlServerDscHelper.psm1') $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` @@ -43,8 +42,11 @@ $TestEnvironment = Initialize-TestEnvironment ` #endregion HEADER function Invoke-TestSetup { - Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs') - Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Global -Force + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SqlPowerShellSqlExecutionException.cs') + + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup { @@ -58,192 +60,143 @@ try Invoke-TestSetup InModuleScope $script:dscResourceName { - InModuleScope 'SqlServerDscHelper' { - $script:dscModuleName = 'SqlServerDsc' - $resourceName = 'MSFT_SqlScriptQuery' - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - GetQuery = "GetQuery;" - TestQuery = "TestQuery;" - SetQuery = "SetQuery;" - } - - $testParametersTimeout = @{ - ServerInstance = $env:COMPUTERNAME - GetQuery = "GetQuery;" - TestQuery = "TestQuery;" - SetQuery = "SetQuery;" - QueryTimeout = 30 - } - - Describe "$resourceName\Get-TargetResource" { - - Context 'Get-TargetResource fails to import SQLPS module' { - $throwMessage = "Failed to import SQLPS module." - - Mock -CommandName Import-SQLPSModule -MockWith { - throw $throwMessage - } - - It 'Should throw the correct error from Import-Module' { - { Get-TargetResource @testParameters } | Should -Throw $throwMessage - } + Describe 'MSFT_SqlScriptQuery\Get-TargetResource' { + BeforeAll { + $testParameters = @{ + ServerInstance = $env:COMPUTERNAME + GetQuery = "GetQuery;" + TestQuery = "TestQuery;" + SetQuery = "SetQuery;" } - Context 'Get-TargetResource returns script results successfully' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } - - It 'Should return the expected results' { - $result = Get-TargetResource @testParameters - - $result.ServerInstance | Should -Be $testParameters.ServerInstance - $result.GetQuery | Should -Be $testParameters.GetQuery - $result.SetQuery | Should -Be $testParameters.SetQuery - $result.TestQuery | Should -Be $testParameters.TestQuery - $result | Should BeOfType Hashtable - } + $testParametersTimeout = @{ + ServerInstance = $env:COMPUTERNAME + GetQuery = "GetQuery;" + TestQuery = "TestQuery;" + SetQuery = "SetQuery;" + QueryTimeout = 30 } + } - Context 'Get-TargetResource returns script results successfully with query timeout' { - Mock -CommandName Import-SQLPSModule - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } - - It 'Should return the expected results' { - $result = Get-TargetResource @testParametersTimeout - $result.ServerInstance | Should -Be $testParametersTimeout.ServerInstance - $result.GetQuery | Should -Be $testParameters.GetQuery - $result.SetQuery | Should -Be $testParameters.SetQuery - $result.TestQuery | Should -Be $testParameters.TestQuery - $result | Should BeOfType Hashtable - } + Context 'Get-TargetResource returns script results successfully' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' } - Context 'Get-TargetResource throws an error when running the script in the GetQuery parameter' { - $errorMessage = "Failed to run SQL Script" - - Mock -CommandName Import-SQLPSModule - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw $errorMessage - } + It 'Should return the expected results' { + $result = Get-TargetResource @testParameters - It 'Should throw the correct error from Invoke-Sqlcmd' { - { Get-TargetResource @testParameters } | Should -Throw $errorMessage - } + $result.ServerInstance | Should -Be $testParameters.ServerInstance + $result.GetQuery | Should -Be $testParameters.GetQuery + $result.SetQuery | Should -Be $testParameters.SetQuery + $result.TestQuery | Should -Be $testParameters.TestQuery + $result | Should BeOfType Hashtable } } - Describe "$resourceName\Set-TargetResource" { - - Context 'Set-TargetResource fails to import SQLPS module' { - $throwMessage = "Failed to import SQLPS module." - - Mock -CommandName Import-SQLPSModule -MockWith { throw $throwMessage } + Context 'Get-TargetResource returns script results successfully with query timeout' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' + } - It 'Should throw the correct error from Import-Module' { - { Set-TargetResource @testParameters } | Should -Throw $throwMessage - } + It 'Should return the expected results' { + $result = Get-TargetResource @testParametersTimeout + $result.ServerInstance | Should -Be $testParametersTimeout.ServerInstance + $result.GetQuery | Should -Be $testParameters.GetQuery + $result.SetQuery | Should -Be $testParameters.SetQuery + $result.TestQuery | Should -Be $testParameters.TestQuery + $result | Should BeOfType Hashtable } + } - Context 'Set-TargetResource runs script without issue' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } + Context 'Get-TargetResource throws an error when running the script in the GetQuery parameter' { + $errorMessage = "Failed to run SQL Script" - It 'Should return the expected results' { - $result = Set-TargetResource @testParameters - $result | Should -Be '' - } + Mock -CommandName Invoke-SqlScript -MockWith { + throw $errorMessage } - Context 'Set-TargetResource runs script without issue using timeout' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - return '' - } + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Get-TargetResource @testParameters } | Should -Throw $errorMessage + } + } + } - It 'Should return the expected results' { - $result = Set-TargetResource @testParametersTimeout - $result | Should -Be '' - } + Describe 'MSFT_SqlScriptQuery\Set-TargetResource' { + Context 'Set-TargetResource runs script without issue' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' } - Context 'Set-TargetResource throws an error when running the script in the SetFilePath parameter' { - $errorMessage = "Failed to run SQL Script" + It 'Should return the expected results' { + $result = Set-TargetResource @testParameters + $result | Should -Be '' + } + } - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw $errorMessage - } + Context 'Set-TargetResource runs script without issue using timeout' { + Mock -CommandName Invoke-SqlScript -MockWith { + return '' + } - It 'Should throw the correct error from Invoke-Sqlcmd' { - { Set-TargetResource @testParameters } | Should -Throw $errorMessage - } + It 'Should return the expected results' { + $result = Set-TargetResource @testParametersTimeout + $result | Should -Be '' } } - Describe "$resourceName\Test-TargetResource" { - Context 'Test-TargetResource fails to import SQLPS module' { - $throwMessage = 'Failed to import SQLPS module.' + Context 'Set-TargetResource throws an error when running the script in the SetFilePath parameter' { + $errorMessage = "Failed to run SQL Script" - Mock -CommandName Import-SQLPSModule -MockWith { - throw $throwMessage - } + Mock -CommandName Invoke-SqlScript -MockWith { + throw $errorMessage + } - It 'Should throw the correct error from Import-Module' { - { Set-TargetResource @testParameters } | Should -Throw $throwMessage - } + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Set-TargetResource @testParameters } | Should -Throw $errorMessage } + } + } - Context 'Test-TargetResource runs script without issue' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith {} + Describe 'MSFT_SqlScriptQuery\Test-TargetResource' { + Context 'Test-TargetResource runs script without issue' { + Mock -CommandName Invoke-SqlScript - It 'Should return true' { - $result = Test-TargetResource @testParameters - $result | Should -Be $true - } + It 'Should return true' { + $result = Test-TargetResource @testParameters + $result | Should -Be $true } + } - Context 'Test-TargetResource runs script without issue with timeout' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith {} + Context 'Test-TargetResource runs script without issue with timeout' { + Mock -CommandName Invoke-SqlScript - It 'Should return true' { - $result = Test-TargetResource @testParametersTimeout - $result | Should -Be $true - } + It 'Should return true' { + $result = Test-TargetResource @testParametersTimeout + $result | Should -Be $true } + } - Context 'Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' { - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw New-Object -TypeName Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException - } + Context 'Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' { + Mock -CommandName Invoke-SqlScript -MockWith { + throw New-Object -TypeName Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException + } - It 'Should return false' { - $result = Test-TargetResource @testParameters - $result | Should -Be $false - } + It 'Should return false' { + $result = Test-TargetResource @testParameters + $result | Should -Be $false } + } - Context 'Test-TargetResource throws an unexpected error when running the script in the TestFilePath parameter' { - $errorMessage = "Failed to run SQL Script" + Context 'Test-TargetResource throws an unexpected error when running the script in the TestFilePath parameter' { + $errorMessage = "Failed to run SQL Script" - Mock -CommandName Import-SQLPSModule -MockWith {} - Mock -CommandName Invoke-Sqlcmd -MockWith { - throw $errorMessage - } + Mock -CommandName Invoke-SqlScript -MockWith { + throw $errorMessage + } - It 'Should throw the correct error from Invoke-Sqlcmd' { - { Test-TargetResource @testParameters } | Should -Throw $errorMessage - } + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Test-TargetResource @testParameters } | Should -Throw $errorMessage } } } diff --git a/Tests/Unit/MSFT_SqlServerEndpoint.Tests.ps1 b/Tests/Unit/MSFT_SqlServerEndpoint.Tests.ps1 index da3b6a81a..86843d44b 100644 --- a/Tests/Unit/MSFT_SqlServerEndpoint.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerEndpoint.Tests.ps1 @@ -39,7 +39,7 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup { diff --git a/Tests/Unit/MSFT_SqlServerEndpointPermission.Tests.ps1 b/Tests/Unit/MSFT_SqlServerEndpointPermission.Tests.ps1 index 000bbcca4..3202edffb 100644 --- a/Tests/Unit/MSFT_SqlServerEndpointPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerEndpointPermission.Tests.ps1 @@ -40,7 +40,7 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlServerEndpointState.Tests.ps1 b/Tests/Unit/MSFT_SqlServerEndpointState.Tests.ps1 index de1eafc9d..7f90c553e 100644 --- a/Tests/Unit/MSFT_SqlServerEndpointState.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerEndpointState.Tests.ps1 @@ -39,8 +39,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - # Loading stub cmdlets - Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlServerLogin.Tests.ps1 b/Tests/Unit/MSFT_SqlServerLogin.Tests.ps1 index a45fb9f61..218f6ea5e 100644 --- a/Tests/Unit/MSFT_SqlServerLogin.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerLogin.Tests.ps1 @@ -43,8 +43,11 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force - Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') + + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlServerMaxDop.Tests.ps1 b/Tests/Unit/MSFT_SqlServerMaxDop.Tests.ps1 index 68a947c48..bdc09f063 100644 --- a/Tests/Unit/MSFT_SqlServerMaxDop.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerMaxDop.Tests.ps1 @@ -30,9 +30,6 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -# Loading mocked classes -Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) - $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` -DSCResourceName $script:dscResourceName ` @@ -42,6 +39,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlServerMemory.Tests.ps1 b/Tests/Unit/MSFT_SqlServerMemory.Tests.ps1 index 2b46e80c1..7b88588a3 100644 --- a/Tests/Unit/MSFT_SqlServerMemory.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerMemory.Tests.ps1 @@ -30,9 +30,6 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -# Loading mocked classes -Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) - $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` -DSCResourceName $script:dscResourceName ` @@ -42,6 +39,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlServerPermission.Tests.ps1 b/Tests/Unit/MSFT_SqlServerPermission.Tests.ps1 index 4ed04687e..92007b2c8 100644 --- a/Tests/Unit/MSFT_SqlServerPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerPermission.Tests.ps1 @@ -40,7 +40,7 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlServerReplication.Tests.ps1 b/Tests/Unit/MSFT_SqlServerReplication.Tests.ps1 index 40a51ba12..cc7080d2f 100644 --- a/Tests/Unit/MSFT_SqlServerReplication.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerReplication.Tests.ps1 @@ -38,11 +38,10 @@ $TestEnvironment = Initialize-TestEnvironment ` -TestType Unit #endregion HEADER -# Begin Testing + try { InModuleScope $script:dscResourceName { - Describe 'Helper functions' { Context 'Get-SqlServerMajorVersion' { diff --git a/Tests/Unit/MSFT_SqlServerSecureConnection.Tests.ps1 b/Tests/Unit/MSFT_SqlServerSecureConnection.Tests.ps1 index 30dbcc64d..bf1d90e51 100644 --- a/Tests/Unit/MSFT_SqlServerSecureConnection.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServerSecureConnection.Tests.ps1 @@ -39,7 +39,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - Import-Module -Name (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Global -Force + # Importing SQLPS stubs + Import-Module -Name (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SQLPSStub.psm1') -Force -Global } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlServiceAccount.Tests.ps1 b/Tests/Unit/MSFT_SqlServiceAccount.Tests.ps1 index 811b68143..a7d4eaf45 100644 --- a/Tests/Unit/MSFT_SqlServiceAccount.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlServiceAccount.Tests.ps1 @@ -39,8 +39,8 @@ $TestEnvironment = Initialize-TestEnvironment ` function Invoke-TestSetup { - # Compile the SMO stubs for use by the unit tests. - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs') -ChildPath 'SMO.cs') } function Invoke-TestCleanup diff --git a/Tests/Unit/MSFT_SqlSetup.Tests.ps1 b/Tests/Unit/MSFT_SqlSetup.Tests.ps1 index 84c79d68a..fdaaef18f 100644 --- a/Tests/Unit/MSFT_SqlSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_SqlSetup.Tests.ps1 @@ -96,26 +96,36 @@ try $value = ($Matches[2] -replace '" "',' ') -replace '"','' } - $null = $argumentHashTable.Add($key, $value) + $argumentHashTable.Add($key, $value) } } $actualValues = $argumentHashTable.Clone() + # Limit the output in the console when everything is fine. + if ($actualValues.Count -ne $ExpectedArgument.Count) + { + Write-Warning -Message 'Verified the setup argument count (expected vs actual)' + Write-Warning -Message ('Expected: {0}' -f ($ExpectedArgument.Keys -join ',')) + Write-Warning -Message ('Actual: {0}' -f ($actualValues.Keys -join ',')) + } + # Start by checking whether we have the same number of parameters - Write-Verbose 'Verifying setup argument count (expected vs actual)' -Verbose - Write-Verbose -Message ('Expected: {0}' -f ($ExpectedArgument.Keys -join ',') ) -Verbose - Write-Verbose -Message ('Actual: {0}' -f ($actualValues.Keys -join ',')) -Verbose + $actualValues.Count | Should -Be $ExpectedArgument.Count ` + -Because ('the expected arguments was: {0}' -f ($ExpectedArgument.Keys -join ',')) - $actualValues.Count | Should -Be $ExpectedArgument.Count + Write-Verbose -Message 'Verified actual setup argument values against expected setup argument values' -Verbose - Write-Verbose 'Verifying actual setup arguments against expected setup arguments' -Verbose foreach ($argumentKey in $ExpectedArgument.Keys) { - $argumentKeyName = $argumentHashTable.GetEnumerator() | Where-Object -FilterScript { $_.Name -eq $argumentKey } | Select-Object -ExpandProperty Name + $argumentKeyName = $actualValues.GetEnumerator() | + Where-Object -FilterScript { + $_.Name -eq $argumentKey + } | Select-Object -ExpandProperty 'Name' + $argumentKeyName | Should -Be $argumentKey - $argumentValue = $argumentHashTable.$argumentKey + $argumentValue = $actualValues.$argumentKey $argumentValue | Should -Be $ExpectedArgument.$argumentKey } } @@ -694,59 +704,10 @@ try ) } - $mockRobocopyExecutableName = 'Robocopy.exe' - $mockRobocopyExecutableVersionWithoutUnbufferedIO = '6.2.9200.00000' - $mockRobocopyExecutableVersionWithUnbufferedIO = '6.3.9600.16384' - $mockRobocopyExecutableVersion = '' # Set dynamically during runtime - $mockRobocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' - $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' - $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' - $mockRobocopyArgumentUseUnbufferedIO = '/J' - $mockRobocopyArgumentSourcePath = 'C:\Source\SQL2016' - $mockRobocopyArgumentDestinationPath = 'D:\Temp' - $mockRobocopyArgumentSourcePathWithSpaces = 'C:\Source\SQL2016 STD SP1' - $mockRobocopyArgumentDestinationPathWithSpaces = 'D:\Temp\DSC SQL2016' - - $mockGetCommand = { - return @( - ( - New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockRobocopyExecutableName -PassThru | - Add-Member -MemberType ScriptProperty -Name FileVersionInfo -Value { - return @( ( New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'ProductVersion' -Value $mockRobocopyExecutableVersion -PassThru -Force - ) ) - } -PassThru -Force - ) - ) - } - - $mockStartSqlSetupProcessExpectedArgument = '' # Set dynamically during runtime - $mockStartSqlSetupProcessExitCode = 0 # Set dynamically during runtime - - $mockStartSqlSetupProcess_Robocopy = { - if ( $ArgumentList -cne $mockStartSqlSetupProcessExpectedArgument ) - { - throw "Expected arguments was not the same as the arguments in the function call.`nExpected: '$mockStartSqlSetupProcessExpectedArgument' `n But was: '$ArgumentList'" - } - - return New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value 0 -PassThru -Force - } - - $mockStartSqlSetupProcess_Robocopy_WithExitCode = { - return New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value $mockStartSqlSetupProcessExitCode -PassThru -Force - } - $mockSourcePathUNCWithoutLeaf = '\\server\share' $mockSourcePathGuid = 'cc719562-0f46-4a16-8605-9f8a47c70402' - $mockNewGuid = { - return New-Object -TypeName Object | - Add-Member -MemberType NoteProperty -Name 'Guid' -Value $mockSourcePathGuid -PassThru -Force - } - $mockGetTemporaryFolder = { + $mockNewTemporaryFolder = { return $mockSourcePathUNC } @@ -967,11 +928,11 @@ try These are written with both lower-case and upper-case to make sure we support that. The feature list must be written in the order it is returned by the function Get-TargetResource. #> - $defaultFeatures = 'SQLEngine,Replication,Dq,Dqc,FullText,Rs,As,Is,Bol,Conn,Bc,Sdk,Mds,Ssms,Adv_Ssms' + $mockDefaultFeatures = 'SQLEngine,Replication,Dq,Dqc,FullText,Rs,As,Is,Bol,Conn,Bc,Sdk,Mds,Ssms,Adv_Ssms' # Default parameters that are used for the It-blocks $mockDefaultParameters = @{ - Features = $defaultFeatures + Features = $mockDefaultFeatures } $MockSqlSvcStartupType = 'Automatic' @@ -987,7 +948,7 @@ try Features = 'SQLEngine' } - Describe "SqlSetup\Get-TargetResource" -Tag 'Get' { + Describe 'SqlSetup\Get-TargetResource' -Tag 'Get' { #region Setting up TestDrive:\ # Local path to TestDrive:\ @@ -1101,8 +1062,8 @@ try } -MockWith $mockEmptyHashtable -Verifiable } - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Connect-UncPath -Verifiable + Mock -CommandName Disconnect-UncPath -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1130,8 +1091,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should -Be $testParameters.InstanceName - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Connect-UncPath -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Disconnect-UncPath -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1219,8 +1180,8 @@ try ) } -Verifiable - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Connect-UncPath -Verifiable + Mock -CommandName Disconnect-UncPath -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance @@ -1281,8 +1242,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should -Be $testParameters.InstanceName - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Connect-UncPath -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Disconnect-UncPath -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1442,8 +1403,8 @@ try } -MockWith $mockEmptyHashtable -Verifiable } - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Connect-UncPath -Verifiable + Mock -CommandName Disconnect-UncPath -Verifiable Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { @@ -1471,8 +1432,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should -Be $testParameters.InstanceName - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Connect-UncPath -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Disconnect-UncPath -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1577,8 +1538,8 @@ try } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable } - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Connect-UncPath -Verifiable + Mock -CommandName Disconnect-UncPath -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance @@ -1685,8 +1646,8 @@ try } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable } - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Connect-UncPath -Verifiable + Mock -CommandName Disconnect-UncPath -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance @@ -1747,8 +1708,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should -Be $testParameters.InstanceName - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Connect-UncPath -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Disconnect-UncPath -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -1931,8 +1892,8 @@ try } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable } - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Connect-UncPath -Verifiable + Mock -CommandName Disconnect-UncPath -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable #region Mock Get-CimInstance @@ -1993,8 +1954,8 @@ try $result = Get-TargetResource @testParameters $result.InstanceName | Should -Be $testParameters.InstanceName - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Connect-UncPath -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Disconnect-UncPath -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It @@ -2555,621 +2516,197 @@ try } Describe "SqlSetup\Test-TargetResource" -Tag 'Test' { - #region Setting up TestDrive:\ - - # Local path to TestDrive:\ - $mockSourcePath = $TestDrive.FullName - - # UNC path to TestDrive:\ - $testDrive_DriveShare = (Split-Path -Path $mockSourcePath -Qualifier) -replace ':','$' - $mockSourcePathUNC = Join-Path -Path "\\localhost\$testDrive_DriveShare" -ChildPath (Split-Path -Path $mockSourcePath -NoQualifier) - - #endregion Setting up TestDrive:\ - - BeforeAll { - # General mocks - Mock -CommandName Get-SqlMajorVersion -MockWith $mockGetSqlMajorVersion -Verifiable - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName -or $Name -eq $mockNamedInstance_InstanceName) - } -MockWith $mockGetItemProperty_SQL -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - ( - $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockDefaultInstance_AnalysisServiceName" -or - $Path -eq "HKLM:\SYSTEM\CurrentControlSet\Services\$mockNamedInstance_AnalysisServiceName" - ) -and - $Name -eq 'ImagePath' - } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable - - # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - - Mock -CommandName Get-Item -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable - - # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - - Mock -CommandName Get-Item -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable - } - - BeforeEach { - Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -Verifiable - } - - # For this test we only need to test one SQL Server version. Mocking SQL Server 2016 for the 'not in the desired state' test. - $mockSqlMajorVersion = 13 - - $mockDefaultInstance_InstanceId = "$($mockSqlDatabaseEngineName)$($mockSqlMajorVersion).$($mockDefaultInstance_InstanceName)" - - $mockSqlInstallPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL" - $mockDynamicSqlBackupPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\Backup" - $mockDynamicSqlTempDatabasePath = '' - $mockDynamicSqlTempDatabaseLogPath = '' - $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" - $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" - - # This sets administrators dynamically in the mock Connect-SQLAnalysis. - $mockDynamicSqlAnalysisAdmins = $mockSqlAnalysisAdmins - Context 'When the system is not in the desired state' { - BeforeEach { - $testParameters = $mockDefaultParameters - $testParameters += @{ - InstanceName = $mockDefaultInstance_InstanceName - SourceCredential = $null - SourcePath = $mockSourcePath - } - - # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_InstanceId_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\DQ\*" - } -MockWith $mockGetItemProperty_DQFeature -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" - } -MockWith $mockGetItemProperty_SqlVersion_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable - } - - It 'Should return that the desired state is absent when no products are installed' { - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should -Be $false - - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - } - - It 'Should return that the desired state is asbent when SSMS product is missing' { - Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - - #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" - } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable - - # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error - Mock -CommandName Get-CimInstance -MockWith { - throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } -Verifiable - #endregion Mock Get-CimInstance - - # Change the default features for this test. - $testParameters.Features = 'SSMS' - - $result = Test-TargetResource @testParameters - $result | Should -Be $false - - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" - } -Exactly -Times 1 -Scope It - #endregion Assert Get-CimInstance - } - - It 'Should return that the desired state is asbent when ADV_SSMS product is missing' { - Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - - #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" - } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable - - # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error - Mock -CommandName Get-CimInstance -MockWith { - throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } -Verifiable - #endregion Mock Get-CimInstance - - # Change the default features for this test. - $testParameters.Features = 'ADV_SSMS' - - $result = Test-TargetResource @testParameters - $result | Should -Be $false - - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" - } -Exactly -Times 1 -Scope It - #endregion Assert Get-CimInstance - } - - It 'Should return that the desired state is absent when a clustered instance cannot be found' { - $testClusterParameters = $testParameters.Clone() + Context 'When no features are installed' { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } - $testClusterParameters += @{ - FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName - FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress - FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } + } -Verifiable } - $result = Test-TargetResource @testClusterParameters + It 'Should return $false' { + $result = Test-TargetResource @testParameters -Verbose + $result | Should -BeFalse - $result | Should -Be $false + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + } } - # This is a test for regression testing of issue #432 - It 'Should return false if a SQL Server failover cluster is missing features' { - $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + Context 'When a clustered instance cannot be found' { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } - Mock -CommandName Get-TargetResource -MockWith { - return @{ - Features = 'SQLENGINE' # Must be upper-case since Get-TargetResource returns upper-case. - FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName - FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress - FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + FailoverClusterGroupName = $null + FailoverClusterNetworkName = $null + FailoverClusterIPAddress = $null } - } -Verifiable + } -Verifiable + } - $testClusterParameters = $testParameters.Clone() - $testClusterParameters['Features'] = 'SQLEngine,AS' + It 'Should return $false' { + $result = Test-TargetResource @testParameters -Verbose + $result | Should -BeFalse - $testClusterParameters += @{ - FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName - FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress - FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' } - - $result = Test-TargetResource @testClusterParameters - $result | Should -Be $false } - } - # For this test we only need to test one SQL Server version. Mocking SQL Server 2014 for the 'in the desired state' test. - $mockSqlMajorVersion = 12 + Context 'When a SQL Server failover cluster is missing features' { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters['Features'] = 'SQLEngine,AS' + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } - Context "When the system is in the desired state" { - BeforeEach { - $testParameters = $mockDefaultParameters - $testParameters += @{ - InstanceName = $mockDefaultInstance_InstanceName - SourceCredential = $null - SourcePath = $mockSourcePath + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = 'SQLENGINE' # Must be upper-case since Get-TargetResource returns upper-case. + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + } -Verifiable } - # Mock all SSMS products. - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) - } -MockWith $mockGetItemProperty_UninstallProducts2008R2 -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) - } -MockWith $mockGetItemProperty_UninstallProducts2012 -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockGetItemProperty_UninstallProducts2014 -Verifiable - - Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - - #region Mock Get-CimInstance - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_DatabaseService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_AgentService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_FullTextService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_ReportingService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" - } -MockWith $mockGetCimInstance_DefaultInstance_IntegrationService -Verifiable - - Mock -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" - } -MockWith $mockGetCimInstance_DefaultInstance_AnalysisService -Verifiable - - # If Get-CimInstance is used in any other way than those mocks with a ParameterFilter, then throw and error - Mock -CommandName Get-CimInstance -MockWith { - throw "Mock Get-CimInstance was called with unexpected parameters. ClassName=$ClassName, Filter=$Filter" - } -Verifiable - #endregion Mock Get-CimInstance - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_InstanceId_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\DQ\*" - } -MockWith $mockGetItemProperty_DQFeature -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" - } -MockWith $mockGetItemProperty_SqlVersion_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" - } -MockWith $mockGetItemProperty_ClientComponentsFull_FeatureList -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable - } + # This is a test for regression testing of issue #432 + It 'Should return $false' { + $result = Test-TargetResource @testParameters -Verbose + $result | Should -BeFalse - It 'Should return that the desired state is present' { - $result = Test-TargetResource @testParameters - $result | Should -Be $true - - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - #region Assert Get-CimInstance - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_DatabaseServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AgentServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_FullTextServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_ReportingServiceName'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$(($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" - } -Exactly -Times 1 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { - $ClassName -eq 'Win32_Service' -and - $Filter -eq "Name = '$mockDefaultInstance_AnalysisServiceName'" - } -Exactly -Times 1 -Scope It - #endregion Assert Get-CimInstance + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + } } + } - It 'Should return that the desired state is present when the correct clustered instance was found' { - $mockCurrentInstanceName = $mockDefaultInstance_InstanceName - - Mock -CommandName Connect-SQL -MockWith $mockConnectSQLCluster -Verifiable + Context "When the system is in the desired state" { + Context 'When all features are installed' { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } - Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResource -Verifiable -ParameterFilter { - $Filter -eq "Type = 'SQL Server'" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = $mockDefaultFeatures + } + } -Verifiable } - Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResourceGroup_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_ResourceGroup' } - - Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResource_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } - - $testClusterParameters = $testParameters.Clone() + It 'Should return $true' { + $result = Test-TargetResource @testParameters -Verbose + $result | Should -BeTrue - $testClusterParameters += @{ - FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName - FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress - FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' } - - $result = Test-TargetResource @testClusterParameters - - $result | Should -Be $true - - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 -Scope It -ParameterFilter { $Filter -eq "Type = 'SQL Server'" } - Assert-MockCalled -CommandName Get-CimAssociatedInstance -Exactly -Times 3 -Scope It } - It 'Should not return false after a clustered install due to the presence of a variable called "FailoverClusterDisks"' { - $mockCurrentInstanceName = $mockDefaultInstance_InstanceName - - Mock -CommandName Connect-SQL -MockWith $mockConnectSQLCluster -Verifiable + Context 'When the correct clustered instance was found' { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } - Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResource -Verifiable -ParameterFilter { - $Filter -eq "Type = 'SQL Server'" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = $mockDefaultFeatures + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + } -Verifiable } - Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResourceGroup_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_ResourceGroup' } - - Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSClusterResource_DefaultInstance -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } - - $testClusterParameters = $testParameters.Clone() + It 'Should return $true' { + $result = Test-TargetResource @testParameters -Verbose + $result | Should -BeTrue - $testClusterParameters += @{ - FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName - FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress - FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' } - $mockDynamicSqlDataDirectoryPath = $mockSqlDataDirectoryPath - $mockDynamicSqlUserDatabasePath = $mockSqlUserDatabasePath - $mockDynamicSqlUserDatabaseLogPath = $mockSqlUserDatabaseLogPath - $mockDynamicSqlTempDatabasePath = $mockSqlTempDatabasePath - $mockDynamicSqlTempDatabaseLogPath = $mockSqlTempDatabaseLogPath - $mockDynamicSqlBackupPath = $mockSqlBackupPath + # Regression test when the variables were detected differently. + It 'Should not return false after a clustered install due to the presence of a variable called "FailoverClusterDisks"' { + # These are needed to populate paths when calling (& $mockClusterDiskMap). + $mockDynamicSqlDataDirectoryPath = $mockSqlDataDirectoryPath + $mockDynamicSqlUserDatabasePath = $mockSqlUserDatabasePath + $mockDynamicSqlUserDatabaseLogPath = $mockSqlUserDatabaseLogPath + $mockDynamicSqlTempDatabasePath = $mockSqlTempDatabasePath + $mockDynamicSqlTempDatabaseLogPath = $mockSqlTempDatabaseLogPath + $mockDynamicSqlBackupPath = $mockSqlBackupPath - New-Variable -Name 'FailoverClusterDisks' -Value (& $mockClusterDiskMap)['UserData'] + New-Variable -Name 'FailoverClusterDisks' -Value (& $mockClusterDiskMap)['UserData'] - $result = Test-TargetResource @testClusterParameters + $result = Test-TargetResource @testParameters -Verbose + $result | Should -BeTrue - $result | Should -Be $true + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' + } } - # This is a test for regression testing of issue #432 - It 'Should return true if a SQL Server failover cluster has all features and is in desired state' { - $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + # This is a test for regression testing of issue #432. + Context 'When the SQL Server failover cluster has all features and is in desired state' { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters['Features'] = 'SQLENGINE,AS' + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } - Mock -CommandName Get-TargetResource -MockWith { - return @{ - Features = 'SQLENGINE,AS' # Must be upper-case since Get-TargetResource returns upper-case. + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = 'SQLEngine,AS' FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName } - } -Verifiable + } -Verifiable + } - $testClusterParameters = $testParameters.Clone() - $testClusterParameters['Features'] = 'SQLEngine,AS' + It 'Should return $true' { + $result = Test-TargetResource @testParameters -Verbose + $result | Should -BeTrue - $testClusterParameters += @{ - FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName - FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress - FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope 'It' } - - $result = Test-TargetResource @testClusterParameters - $result | Should -Be $true } } @@ -3234,11 +2771,9 @@ try return $true } -Verifiable - <#1 - These mock should not have Verifiable because they are used to test so we never - call them in Assert-MockCalled. - #> - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL + Mock -CommandName Test-PendingRestart -MockWith { + return $false + } # Mock PsDscRunAsCredential context. $PsDscContext = @{ @@ -3247,8 +2782,6 @@ try } BeforeEach { - Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis - $testParameters = $mockDefaultParameters.Clone() $testParameters += @{ SQLSysAdminAccounts = 'COMPANY\User1','COMPANY\SQLAdmins' @@ -3273,23 +2806,11 @@ try Context "When setup process fails with exit code " { BeforeEach { Mock -CommandName Start-SqlSetupProcess -MockWith $mockStartSqlSetupProcess_WithDynamicExitCode -Verifiable - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Copy-ItemWithRobocopy -Verifiable - Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable - Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } + } -Verifiable $testParameters += @{ InstanceName = $mockDefaultInstance_InstanceName @@ -3327,23 +2848,13 @@ try Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { BeforeEach { - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Copy-ItemWithRobocopy -Verifiable - Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable - Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } + } -Verifiable - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Invoke-InstallationMediaCopy -MockWith $mockNewTemporaryFolder } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -3368,11 +2879,11 @@ try AsSvcStartupType = $mockAsSvcStartupType IsSvcStartupType = $mockIsSvcStartupType RsSvcStartupType = $mockRsSvcStartupType - SqlTempdbFileCount = 2 - SqlTempdbFileSize = 128 - SqlTempdbFileGrowth = 128 - SqlTempdbLogFileSize = 128 - SqlTempdbLogFileGrowth = 128 + SqlTempDbFileCount = 2 + SqlTempDbFileSize = 128 + SqlTempDbFileGrowth = 128 + SqlTempDbLogFileSize = 128 + SqlTempDbLogFileGrowth = 128 } if ( $mockSqlMajorVersion -in (13,14) ) @@ -3401,38 +2912,16 @@ try AsSvcStartupType = $mockAsSvcStartupType IsSvcStartupType = $mockIsSvcStartupType RsSvcStartupType = $mockRsSvcStartupType - SqlTempdbFileCount = 2 - SqlTempdbFileSize = 128 - SqlTempdbFileGrowth = 128 - SqlTempdbLogFileSize = 128 - SqlTempdbLogFileGrowth = 128 + SqlTempDbFileCount = 2 + SqlTempDbFileSize = 128 + SqlTempDbFileGrowth = 128 + SqlTempDbLogFileSize = 128 + SqlTempDbLogFileGrowth = 128 } { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName New-Guid -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - + Assert-MockCalled -CommandName Invoke-InstallationMediaCopy -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Import-SQLPSModule -Exactly -Times 1 -Scope It @@ -3487,24 +2976,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3528,25 +2999,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3566,40 +3018,13 @@ try $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' } - # Mocking SharedDirectory (when previously installed and should not be installed again). - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - - # Mocking SharedWowDirectory (when previously installed and should not be installed again). - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Copy-ItemWithRobocopy -Verifiable - Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable - Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } } -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" - } -Verifiable + Mock -CommandName Invoke-InstallationMediaCopy -MockWith $mockNewTemporaryFolder -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -3616,30 +3041,7 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 2 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 2 -Scope It - Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName New-Guid -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - + Assert-MockCalled -CommandName Invoke-InstallationMediaCopy -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3675,25 +3077,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3711,23 +3094,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3749,30 +3115,13 @@ try $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' } - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Copy-ItemWithRobocopy -Verifiable - Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable - Mock -CommandName New-Guid -MockWith $mockNewGuid -Verifiable - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } } -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" - } -Verifiable + Mock -CommandName Invoke-InstallationMediaCopy -MockWith $mockNewTemporaryFolder -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { @@ -3789,30 +3138,7 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 2 -Scope It - Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 2 -Scope It - Assert-MockCalled -CommandName Get-TemporaryFolder -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName New-Guid -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Copy-ItemWithRobocopy -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - + Assert-MockCalled -CommandName Invoke-InstallationMediaCopy -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Import-SQLPSModule -Exactly -Times 1 -Scope It @@ -3849,25 +3175,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3885,23 +3192,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -3931,24 +3221,10 @@ try $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' } - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" - } -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } } -Verifiable } @@ -3966,25 +3242,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -4020,24 +3277,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -4055,23 +3294,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2014_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or - $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) - } -Exactly -Times 6 -Scope It - Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } @@ -4093,44 +3315,10 @@ try FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName } - Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResourceGroup_AvailableStorage -ParameterFilter { - $Filter -eq "Name = 'Available Storage'" - } -Verifiable - - Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSCluster_ResourceGroupToResource -ParameterFilter { - ($Association -eq 'MSCluster_ResourceGroupToResource') -and ($ResultClassName -eq 'MSCluster_Resource') - } -Verifiable - - Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSCluster_ResourceToPossibleOwner -ParameterFilter { - $Association -eq 'MSCluster_ResourceToPossibleOwner' - } -Verifiable - - Mock -CommandName Get-CimAssociatedInstance -MockWith $mockGetCimAssociatedInstance_MSCluster_DiskPartition -ParameterFilter { - $ResultClassName -eq 'MSCluster_DiskPartition' - } -Verifiable - - Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterNetwork -ParameterFilter { - ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_Network') -and ($Filter -eq 'Role >= 2') - } -Verifiable - - Mock -CommandName Get-CimInstance -MockWith $mockGetCIMInstance_MSCluster_ClusterSharedVolume -ParameterFilter { - $ClassName -eq 'MSCluster_ClusterSharedVolume' - } -Verifiable - - Mock -CommandName Get-CimInstance -MockWith $mockGetCIMInstance_MSCluster_ClusterSharedVolumeToResource -ParameterFilter { - $ClassName -eq 'MSCluster_ClusterSharedVolumeToResource' - } -Verifiable - - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - } - - BeforeEach { - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" - } -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } } -Verifiable } @@ -4181,6 +3369,12 @@ try SQLBackupDir = $mockDynamicSqlBackupPath } + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } + } -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResourceGroup_AvailableStorage -ParameterFilter { $Filter -eq "Name = 'Available Storage'" } -Verifiable @@ -4208,31 +3402,6 @@ try Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterNetwork -ParameterFilter { ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_Network') -and ($Filter -eq 'Role >= 2') } -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_InstanceId_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\DQ\*" - } -MockWith $mockGetItemProperty_DQFeature -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" - } -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable - - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - } - - BeforeEach { - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" - } -Verifiable } It 'Should pass proper parameters to setup' { @@ -4497,29 +3666,13 @@ try Action = 'PrepareFailoverCluster' } - Mock -CommandName New-SmbMapping -Verifiable - Mock -CommandName Remove-SmbMapping -Verifiable - Mock -CommandName Copy-ItemWithRobocopy -Verifiable - Mock -CommandName Get-TemporaryFolder -MockWith $mockGetTemporaryFolder -Verifiable - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_InstanceId_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\DQ\*" - } -MockWith $mockGetItemProperty_DQFeature -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } } -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable - - Mock -CommandName Start-SqlSetupProcess -MockWith $mockStartSqlSetupProcess -Verifiable + Mock -CommandName Invoke-InstallationMediaCopy -MockWith $mockNewTemporaryFolder -Verifiable Mock -CommandName Get-CimInstance -ParameterFilter { ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_ResourceGroup') -and ($Filter -eq "Name = 'Available Storage'") @@ -4559,13 +3712,6 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw - Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Connect-SQLAnalysis -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -and - ($Name -eq $mockDefaultInstance_InstanceName) - } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Start-SqlSetupProcess -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It @@ -4618,12 +3764,10 @@ try SQLBackupDir = $mockDynamicSqlBackupPath } - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" - } -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = '' + } } -Verifiable Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResourceGroup_AvailableStorage -ParameterFilter { @@ -4653,8 +3797,6 @@ try Mock -CommandName Get-CimInstance -MockWith $mockGetCIMInstance_MSCluster_ClusterSharedVolumeToResource -ParameterFilter { $ClassName -eq 'MSCluster_ClusterSharedVolumeToResource' } -Verifiable - - Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable } It 'Should throw an error when one or more paths are not resolved to clustered storage' { @@ -4667,7 +3809,6 @@ try } It 'Should properly map paths to clustered disk resources' { - $mockStartSqlSetupProcessExpectedArgument = $mockStartSqlSetupProcessExpectedArgumentClusterDefault.Clone() $mockStartSqlSetupProcessExpectedArgument += @{ Action = 'CompleteFailoverCluster' @@ -4731,7 +3872,6 @@ try } It 'Should build a valid IP address string for a single address' { - $mockStartSqlSetupProcessExpectedArgument = $mockStartSqlSetupProcessExpectedArgumentClusterDefault.Clone() $mockStartSqlSetupProcessExpectedArgument += @{ FailoverClusterIPAddresses = $mockDefaultInstance_FailoverClusterIPAddressParameter_SingleSite @@ -4800,221 +3940,11 @@ try { Set-TargetResource @testParameters } | Should -Not -Throw } } - } Assert-VerifiableMock } - # Tests only the parts of the code that does not already get tested thru the other tests. - Describe 'Copy-ItemWithRobocopy' -Tag 'Helper' { - Context 'When Copy-ItemWithRobocopy is called it should return the correct arguments' { - BeforeEach { - Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable - Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy -Verifiable - $mockRobocopyArgumentSourcePathQuoted = '"{0}"' -f $mockRobocopyArgumentSourcePath - $mockRobocopyArgumentDestinationPathQuoted = '"{0}"' -f $mockRobocopyArgumentDestinationPath - } - - - It 'Should use Unbuffered IO when copying' { - $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO - - $mockStartSqlSetupProcessExpectedArgument = - $mockRobocopyArgumentSourcePathQuoted, - $mockRobocopyArgumentDestinationPathQuoted, - $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty, - $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, - $mockRobocopyArgumentUseUnbufferedIO, - $mockRobocopyArgumentSilent -join ' ' - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - - It 'Should not use Unbuffered IO when copying' { - $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithoutUnbufferedIO - - $mockStartSqlSetupProcessExpectedArgument = - $mockRobocopyArgumentSourcePathQuoted, - $mockRobocopyArgumentDestinationPathQuoted, - $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty, - $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, - '', - $mockRobocopyArgumentSilent -join ' ' - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - } - - Context 'When Copy-ItemWithRobocopy throws an exception it should return the correct error messages' { - BeforeEach { - $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO - - Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable - Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy_WithExitCode -Verifiable - } - - It 'Should throw the correct error message when error code is 8' { - $mockStartSqlSetupProcessExitCode = 8 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Throw "Robocopy reported errors when copying files. Error code: $mockStartSqlSetupProcessExitCode." - - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - - It 'Should throw the correct error message when error code is 16' { - $mockStartSqlSetupProcessExitCode = 16 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Throw "Robocopy reported errors when copying files. Error code: $mockStartSqlSetupProcessExitCode." - - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - - It 'Should throw the correct error message when error code is greater than 7 (but not 8 or 16)' { - $mockStartSqlSetupProcessExitCode = 9 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Throw "Robocopy reported that failures occurred when copying files. Error code: $mockStartSqlSetupProcessExitCode." - - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - } - - Context 'When Copy-ItemWithRobocopy is called and finishes successfully it should return the correct exit code' { - BeforeEach { - $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO - - Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable - Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy_WithExitCode -Verifiable - } - - AfterEach { - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - - It 'Should finish successfully with exit code 1' { - $mockStartSqlSetupProcessExitCode = 1 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - - It 'Should finish successfully with exit code 2' { - $mockStartSqlSetupProcessExitCode = 2 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - - It 'Should finish successfully with exit code 3' { - $mockStartSqlSetupProcessExitCode = 3 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePath - DestinationPath = $mockRobocopyArgumentDestinationPath - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - } - } - Context 'When Copy-ItemWithRobocopy is called with spaces in paths and finishes successfully it should return the correct exit code' { - BeforeEach { - $mockRobocopyExecutableVersion = $mockRobocopyExecutableVersionWithUnbufferedIO - - Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable - Mock -CommandName Start-Process -MockWith $mockStartSqlSetupProcess_Robocopy_WithExitCode -Verifiable - } - - AfterEach { - Assert-MockCalled -CommandName Get-Command -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Start-Process -Exactly -Times 1 -Scope It - } - - It 'Should finish successfully with exit code 1' { - $mockStartSqlSetupProcessExitCode = 1 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePathWithSpaces - DestinationPath = $mockRobocopyArgumentDestinationPathWithSpaces - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - - } - - It 'Should finish successfully with exit code 2' { - $mockStartSqlSetupProcessExitCode = 2 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePathWithSpaces - DestinationPath = $mockRobocopyArgumentDestinationPathWithSpaces - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - - } - - It 'Should finish successfully with exit code 3' { - $mockStartSqlSetupProcessExitCode = 3 - - $copyItemWithRobocopyParameter = @{ - Path = $mockRobocopyArgumentSourcePathWithSpaces - DestinationPath = $mockRobocopyArgumentDestinationPathWithSpaces - } - - { Copy-ItemWithRobocopy @copyItemWithRobocopyParameter } | Should -Not -Throw - } - } - } - Describe 'Get-ServiceAccountParameters' -Tag 'Helper' { $serviceTypes = @('SQL','AGT','IS','RS','AS','FT') @@ -5083,45 +4013,6 @@ try } } - Describe 'Get-TemporaryFolder' -Tag 'Helper' { - BeforeAll { - $mockExpectedTempPath = [IO.Path]::GetTempPath() - } - - Context 'When using Get-TemporaryFolder' { - It 'Should return the correct temporary path' { - Get-TemporaryFolder | Should -BeExactly $mockExpectedTempPath - } - } - } - - Describe 'Start-SqlSetupProcess' -Tag 'Helper' { - Context 'When starting a process successfully' { - It 'Should return exit code 0' { - $startSqlSetupProcessParameters = @{ - FilePath = 'powershell.exe' - ArgumentList = '-Command &{Start-Sleep -Seconds 2}' - Timeout = 30 - } - - $processExitCode = Start-SqlSetupProcess @startSqlSetupProcessParameters - $processExitCode | Should -BeExactly 0 - } - } - - Context 'When starting a process and the process does not finish before the timeout period' { - It 'Should throw an error message' { - $startSqlSetupProcessParameters = @{ - FilePath = 'powershell.exe' - ArgumentList = '-Command &{Start-Sleep -Seconds 3}' - Timeout = 2 - } - - { Start-SqlSetupProcess @startSqlSetupProcessParameters } | Should -Throw - } - } - } - Describe 'Get-InstalledSharedFeatures' -Tag 'Helper' { Context 'When there are no shared features installed' { BeforeAll {