diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 602571445..5a8f59e62 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,9 @@ -_Please provide the following information regarding your issue (place N/A if certain fields don't apply in your case:_ + +_Your feedback and support is greatly appreciated, thanks for contributing! If you like to contribute more please feel free to read the [contributing section](https://github.com/PowerShell/xSQLServer#contributing)._ + +Please prefix the issue title with the resource name, i.e. 'xSQLServerSetup: Short description of my issue' + +Please provide the following information regarding your issue (place N/A if the fields that don't apply to your issue): ---- DELETE THIS LINE AND ABOVE ---- @@ -9,10 +14,3 @@ _Please provide the following information regarding your issue (place N/A if cer **Version of the Operating System, SQL Server and PowerShell the DSC Target Node is running:** **Version of the DSC module you're using, or 'dev' if you're using current dev branch:** - ----- DELETE THIS LINE AND BELOW ---- - -_Your feedback and support is greatly appreciated._ - -_If you are able to resolve this issue or add new features, you may submit a Pull Request against this project._ -_Please see the [Contribution Guideliness](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) for information on how to contribute._ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9f27e0cf7..8556ec240 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,25 +1,22 @@ -Thanks for submitting a Pull Request (PR) to this project. -This template will help you create your pull request (PR). +_Thanks for submitting a Pull Request (PR) to this project. Your contribution to this project is greatly appreciated!_ -Please make sure you have read the [Contribution Guidelines](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md). +Please make sure you have read the [contributing section](https://github.com/PowerShell/xSQLServer#contributing) -To aid community reviewers in reviewing and merging your pull request (PR), please take the time to run through the below checklist +Please prefix the PR title with the resource name, i.e. 'xSQLServerSetup: My short description' +If this is a breaking change, then also prefix the PR title with 'BREAKING CHANGE:', i.e. 'BREAKING CHANGE: xSQLServerSetup: My short description' -Your contribution to this project is greatly appreciated! +To aid community reviewers in reviewing and merging your PR, please take the time to run through the below checklist. +Change to [x] for each task in the task list that applies to this PR. ----- DELETE THIS LINE AND ABOVE ---- +---- DELETE THIS LINE AND EVERYTHING ABOVE ---- [Replace this with a description of your pull request] This Pull Request (PR) fixes the following issues: -[Replace this with the list of issues. Use format: Fixes #123] +[Replace this with the list of issues or n/a. Use format: Fixes #123] - [ ] Change details added to Unreleased section of CHANGELOG.md? - [ ] Added/updated documentation, comment-based help and descriptions in .schema.mof files where appropriate? - [ ] Examples appropriately updated? - [ ] New/changed code adheres to [Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md)? - [ ] [Unit and (optional) Integration tests](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md) created/updated where possible? - ----- DELETE THIS LINE AND BELOW ---- - -Your contribution to this project is greatly appreciated. diff --git a/.gitignore b/.gitignore index db5e4b56d..c333d23d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -DSCResource.Tests +DSCResource.Tests .vs .vscode +node_modules diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 000000000..bad263958 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,10 @@ +{ + "default": true, + "MD029": { + "style": "ordered" + }, + "MD034": false, + "MD024": false, + "MD013": false, + "no-hard-tabs": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e6f5815..43403fe9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,114 @@ ## Unreleased +## 5.0.0.0 + +- Improvements how tests are initiated in AppVeyor + - Removed previous workaround (issue #201) from unit tests. + - Changes in appveyor.yml so that SQL modules are removed before common test is run. + - Now the deploy step are no longer failing when merging code into Dev. Neither is the deploy step failing if a contributor had AppVeyor connected to the fork of xSQLServer and pushing code to the fork. +- Changes to README.md + - Changed the contributing section to help new contributors. + - Added links for each resource so it is easier to navigate to the parameter list for each resource. + - Moved the list of resources in alphabetical order. + - Moved each resource parameter list into alphabetical order. + - Removed old text mentioning System Center. + - Now the correct product name is written in the installation section, and a typo was also fixed. + - Fixed a typo in the Requirements section. + - Added link to Examples folder in the Examples section. + - Change the layout of the README.md to closer match the one of PSDscResources + - Added more detailed text explaining what operating systemes WMF5.0 can be installed on. + - Verified all resource schema files with the README.md and fixed some errors (descriptions was not verified). + - Added security requirements section for resource xSQLServerEndpoint and xSQLAOGroupEnsure. +- Changes to xSQLServerSetup + - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. + - Now it uses CIM cmdlets to get information from WMI classes. + - Resolved all of the PSScriptAnalyzer warnings that was triggered in the common tests. + - Improvement for service accounts to enable support for Managed Service Accounts as well as other nt authority accounts + - Changes to the helper function Copy-ItemWithRoboCopy + - Robocopy is now started using Start-Process and the error handling has been improved. + - Robocopy now removes files at the destination path if they no longer exists at the source. + - Robocopy copies using unbuffered I/O when available (recommended for large files). + - Added a more descriptive text for the parameter `SourceCredential` to further explain how the parameter work. + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Function `NetUse` has been replaced with `New-SmbMapping` and `Remove-SmbMapping`. + - Renamed function `GetSQLVersion` to `Get-SqlMajorVersion`. + - BREAKING CHANGE: Renamed parameter PID to ProductKey to avoid collision with automatic variable $PID +- Changes to xSQLServerScript + - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. + - It is now possible to configure two instances on the same node, with the same script. + - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. + - Added examples to show how to authenticate using either SQL or Windows authentication. + - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) +- Changes to xSQLServerFirewall + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Adding new optional parameter SourceCredential that can be used to authenticate against SourcePath. + - Solved PSSA rules errors in the code. + - Get-TargetResource no longer return $true when no products was installed. +- Changes to the unit test for resource + - xSQLServerSetup + - Added test coverage for helper function Copy-ItemWithRoboCopy +- Changes to xSQLServerLogin + - Removed ShouldProcess statements + - Added the ability to enforce password policies on SQL logins +- Added common test (xSQLServerCommon.Tests) for xSQLServer module + - Now all markdown files will be style checked when tests are running in AppVeyor after sending in a pull request. + - Now all [Examples](/Examples/Resources) will be tested by compiling to a .mof file after sending in a pull request. +- Changes to xSQLServerDatabaseOwner + - The example 'SetDatabaseOwner' can now compile, it wrongly had a `DependsOn` in the example. +- Changes to SQLServerRole + - The examples 'AddServerRole' and 'RemoveServerRole' can now compile, it wrongly had a `DependsOn` in the example. +- Changes to CONTRIBUTING.md + - Added section 'Tests for examples files' + - Added section 'Tests for style check of Markdown files' + - Added section 'Documentation with Markdown' + - Added texts to section 'Tests' +- Changes to xSQLServerHelper + - added functions + - Get-SqlDatabaseRecoveryModel + - Set-SqlDatabaseRecoveryModel +- Examples + - xSQLServerDatabaseRecoveryModel + - 1-SetDatabaseRecoveryModel.ps1 + - xSQLServerDatabasePermission + - 1-GrantDatabasePermissions.ps1 + - 2-RevokeDatabasePermissions.ps1 + - 3-DenyDatabasePermissions.ps1 + - xSQLServerFirewall + - 1-CreateInboundFirewallRules + - 2-RemoveInboundFirewallRules +- Added tests for resources + - xSQLServerDatabaseRecoveryModel + - xSQLServerDatabasePermissions + - xSQLServerFirewall +- Changes to xSQLServerDatabaseRecoveryModel + - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include SQLServer, and SQLInstanceName. +- Changes to xSQLServerDatabasePermission + - BREAKING CHANGE: Renamed xSQLServerDatabasePermissions to xSQLServerDatabasePermission to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include PermissionState, SQLServer, and SQLInstanceName. +- Added support for clustered installations to xSQLServerSetup + - Migrated relevant code from xSQLServerFailoverClusterSetup + - Removed Get-WmiObject usage + - Clustered storage mapping now supports asymmetric cluster storage + - Added support for multi-subnet clusters + - Added localized error messages for cluster object mapping + - Updated README.md to reflect new parameters +- Updated description for xSQLServerFailoverClusterSetup to indicate it is deprecated. +- xPDT helper module + - Function GetxPDTVariable was removed since it no longer was used by any resources. + - File xPDT.xml was removed since it was not used by any resources, and did not provide any value to the module. +- Changes xSQLServerHelper moduled + - Removed the globally defined `$VerbosePreference = 'Continue'` from xSQLServerHelper. + - Fixed a typo in a variable name in the function New-ListenerADObject. + - Now Restart-SqlService will correctly show the services it restarts. Also fixed PSSA warnings. + ## 4.0.0.0 - Fixes in xSQLServerConfiguration diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad94268c0..358c405df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,28 +73,40 @@ one resource, then the functions can also be placed in the common [xSQLServerHel ### Tests +For a review of a Pull Request (PR) to start, all tests must pass without error. If you need help to figure why some test don't pass, just write a comment in the Pull Request (PR), or submit an issue, and somebody will come along and assist. + +To run all tests manually run the following. + +```powershell +Install-Module Pester +cd '\Tests' +Invoke-Pester +``` + +#### Tests for style check of Markdown files + +When sending in a Pull Request (PR) a style check will be performed on all Markdown files, and if the tests find any error the build will fail. +See the section [Documentation with Markdown](#documentation-with-markdown) how these errors kan be found before sending in the PR. + +The Markdown tests can be run locally if the packet manager 'npm' is available. To have npm available you need to install [node.js](https://nodejs.org/en/download/). +If 'npm' is not available, a warning text will print and the rest of the tests will continue run. + +#### Tests for examples files + +When sending in a Pull Request (PR) all example files will be tested so they can be compiled to a .mof file. If the tests find any errors the build will fail. +Before the test runs in AppVeyor the module will be copied to the first path of `$env:PSModulePath`. +To run this test locally, make sure you have the xSQLServer module deployed to a path where it can be used. See `$env:PSModulePath` to view the existing paths. + #### Using SMO stub classes There are [stub classes](https://github.com/PowerShell/xSQLServer/blob/dev/Tests/Unit/Stubs/SMO.cs) for the SMO classes which can be used and improved on when creating tests where SMO classes are used in the code being tested. #### AppVeyor -AppVeyor is the platform where the tests is run when sending in a Pull Request (PR). Due to a change in the build worker that AppVeyor provides it has already have the SMO assemblies loaded, which make our stub SMO classes unable to be initiated. -To get around this we need to get a clean PowerShell environment to run our tests in. One way is to use `Start-Job`. So this change needs to be done to the unit test template before sending in a Pull Request (PR). +AppVeyor is the platform where the tests is run when sending in a Pull Request (PR). All tests are run on a clean AppVeyor build worker for each push to the Pull Request (PR). +The tests that are run on the build worker are common tests, unit tests and integration tests (with some limitations). -```powershell -# - AppVeyor build worker loads the SMO assembly which makes the SMO stub classes unable to be initiated. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - # Unit test template goes here -} - -$testJob | Receive-Job -Wait -``` +### Documentation with Markdown + +If using Visual Studio Code to edit Markdown files it can be a good idea to install the markdownlint extension. It will help to do style checking. +The file [.markdownlint.json](/.markdownlint.json) is prepared with a default set of rules which will automatically be used by the extension. diff --git a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.psm1 b/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.psm1 deleted file mode 100644 index 567195c87..000000000 --- a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.psm1 +++ /dev/null @@ -1,108 +0,0 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Verbose -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [parameter(Mandatory = $true)] - [ValidateSet("Full","Simple","BulkLogged")] - [System.String] - $RecoveryModel = "Full", - - [parameter(Mandatory = $true)] - [System.String] - $SqlServerInstance, - - [parameter(Mandatory = $true)] - [System.String] - $DatabaseName - ) - - $SqlServerInstance = $SqlServerInstance.Replace('\MSSQLSERVER','') - New-VerboseMessage -Message "Checking Database $DatabaseName recovery mode for $RecoveryModel" - - $db = Get-SqlDatabase -ServerInstance $SqlServerInstance -Name $DatabaseName - $value = ($db.RecoveryModel -eq $RecoveryModel) - New-VerboseMessage -Message "Database $DatabaseName recovery mode comparison $value." - - $returnValue = @{ - RecoveryModel = $db.RecoveryModel - SqlServerInstance = $SqlServerInstance - DatabaseName = $DatabaseName - } - - $returnValue -} - - -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateSet("Full","Simple","BulkLogged")] - [System.String] - $RecoveryModel = "Full", - - [parameter(Mandatory = $true)] - [System.String] - $SqlServerInstance, - - [parameter(Mandatory = $true)] - [System.String] - $DatabaseName - ) - - $SqlServerInstance = $SqlServerInstance.Replace('\MSSQLSERVER','') - $db = Get-SqlDatabase -ServerInstance $SqlServerInstance -Name $DatabaseName - New-VerboseMessage -Message "Database $DatabaseName recovery mode is $db.RecoveryModel." - - if($db.RecoveryModel -ne $RecoveryModel) - { - $db.RecoveryModel = $RecoveryModel; - $db.Alter(); - New-VerboseMessage -Message "DB $DatabaseName recovery mode is changed to $RecoveryModel." - } - - if(!(Test-TargetResource @PSBoundParameters)) - { - throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult - } -} - - -function Test-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [parameter(Mandatory = $true)] - [ValidateSet("Full","Simple","BulkLogged")] - [System.String] - $RecoveryModel = "Full", - - [parameter(Mandatory = $true)] - [System.String] - $SqlServerInstance, - - [parameter(Mandatory = $true)] - [System.String] - $DatabaseName - ) - $SqlServerInstance = $SqlServerInstance.Replace('\MSSQLSERVER','') - $result = ((Get-TargetResource @PSBoundParameters).RecoveryModel -eq $RecoveryModel) - - $result -} - - -Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.schema.mof b/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.schema.mof deleted file mode 100644 index 557166e42..000000000 --- a/DSCResources/MSFT_xSQLDatabaseRecoveryModel/MSFT_xSQLDatabaseRecoveryModel.schema.mof +++ /dev/null @@ -1,8 +0,0 @@ - -[ClassVersion("1.0"), FriendlyName("xSQLDatabaseRecoveryModel")] -class MSFT_xSQLDatabaseRecoveryModel : OMI_BaseResource -{ - [Key, Description("The SQL database name")] String DatabaseName; - [Required, Description("The SQL server and instance.")] String SqlServerInstance; - [Required, Description("Recovery Model"), ValueMap{"Full","Simple","BulkLogged"}, Values{"Full","Simple","BulkLogged"}] String RecoveryModel; -}; diff --git a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 new file mode 100644 index 000000000..f0614f322 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 @@ -0,0 +1,278 @@ +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Force + +<# + .SYNOPSIS + Returns the current permissions for the user in the database + + .PARAMETER Ensure + This is The Ensure if the permission should be granted (Present) or revoked (Absent) + Not used in Get-TargetResource + + .PARAMETER Database + This is the SQL database + + .PARAMETER Name + This is the name of the SQL login for the permission set + + .PARAMETER PermissionState + This is the state of permission set. Valid values are 'Grant' or 'Deny' + + .PARAMETER Permissions + This is a list that represents a SQL Server set of database permissions + + .PARAMETER SQLServer + This is the SQL Server for the database + + .PARAMETER SQLInstanceName + This is the SQL instance for the database +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [ValidateSet('Present','Absent')] + [System.String] + $Ensure, + + [parameter(Mandatory = $true)] + [System.String] + $Database, + + [parameter(Mandatory = $true)] + [System.String] + $Name, + + [parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [System.String[]] + $Permissions, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName = 'MSSQLSERVER' + ) + + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sqlServerObject) + { + Write-Verbose -Message "Getting permissions for user '$Name' in database '$Database'" + $getSqlDatabasePermissionResult = Get-SqlDatabasePermission -SqlServerObject $sqlServerObject ` + -Name $Name ` + -Database $Database ` + -PermissionState $PermissionState + + if ($getSqlDatabasePermissionResult) + { + $resultOfPermissionCompare = Compare-Object -ReferenceObject $Permissions ` + -DifferenceObject $getSqlDatabasePermissionResult + if ($null -eq $resultOfPermissionCompare) + { + $Ensure = 'Present' + } + else + { + $Ensure = 'Absent' + } + } + else + { + $Ensure = 'Absent' + } + } + else + { + throw New-TerminatingError -ErrorType ConnectSQLError ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidOperation + } + + $returnValue = @{ + Ensure = $Ensure + Database = $Database + Name = $Name + PermissionState = $PermissionState + Permissions = $getSqlDatabasePermissionResult + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + } + + $returnValue +} + +<# + .SYNOPSIS + Sets the permissions for the user in the database. + + .PARAMETER Ensure + This is The Ensure if the permission should be granted (Present) or revoked (Absent) + + .PARAMETER Database + This is the SQL database + + .PARAMETER Name + This is the name of the SQL login for the permission set + + .PARAMETER PermissionState + This is the state of permission set. Valid values are 'Grant' or 'Deny' + + .PARAMETER Permissions + This is a list that represents a SQL Server set of database permissions + + .PARAMETER SQLServer + This is the SQL Server for the database + + .PARAMETER SQLInstanceName + This is the SQL instance for the database +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present', + + [parameter(Mandatory = $true)] + [System.String] + $Database, + + [parameter(Mandatory = $true)] + [System.String] + $Name, + + [parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [System.String[]] + $Permissions, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName = 'MSSQLSERVER' + ) + + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sqlServerObject) + { + Write-Verbose -Message "Setting permissions of database '$Database' for login '$Name'" + + if ($Ensure -eq 'Present') + { + Add-SqlDatabasePermission -SqlServerObject $sqlServerObject ` + -Name $Name ` + -Database $Database ` + -PermissionState $PermissionState ` + -Permissions $Permissions + + New-VerboseMessage -Message "$PermissionState - SQL Permissions for $Name, successfullly added in $Database" + } + else + { + Remove-SqlDatabasePermission -SqlServerObject $sqlServerObject ` + -Name $Name ` + -Database $Database ` + -PermissionState $PermissionState ` + -Permissions $Permissions + + New-VerboseMessage -Message "$PermissionState - SQL Permissions for $Name, successfullly removed in $Database" + } + } + else + { + throw New-TerminatingError -ErrorType ConnectSQLError ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidOperation + } +} + +<# + .SYNOPSIS + Tests if the permissions is set for the user in the database + + .PARAMETER Ensure + This is The Ensure if the permission should be granted (Present) or revoked (Absent) + + .PARAMETER Database + This is the SQL database + + .PARAMETER Name + This is the name of the SQL login for the permission set + + .PARAMETER PermissionState + This is the state of permission set. Valid values are 'Grant' or 'Deny' + + .PARAMETER Permissions + This is a list that represents a SQL Server set of database permissions + + .PARAMETER SQLServer + This is the SQL Server for the database + + .PARAMETER SQLInstanceName + This is the SQL instance for the database +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present', + + [parameter(Mandatory = $true)] + [System.String] + $Database, + + [parameter(Mandatory = $true)] + [System.String] + $Name, + + [parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [System.String[]] + $Permissions, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName = 'MSSQLSERVER' + ) + + Write-Verbose -Message "Evaluating permissions for user '$Name' in database '$Database'." + + $getTargetResourceResult = Get-TargetResource @PSBoundParameters + + return Test-SQLDscParameterState -CurrentValues $getTargetResourceResult ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @('Name', 'Ensure', 'PermissionState', 'Permissions') +} + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof new file mode 100644 index 000000000..5d58b58a1 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof @@ -0,0 +1,11 @@ +[ClassVersion("1.0.0.0"), FriendlyName("xSQLServerDatabasePermission")] +class MSFT_xSQLServerDatabasePermission : OMI_BaseResource +{ + [Write, Description("If the values should be present or absent. Valid values are 'Present' or 'Absent'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("The name of the database.")] String Database; + [Key, Description("The name of the user that should be granted or denied the permission.")] String Name; + [Key, Description("The state of the permission. Valid values are 'Grant' or 'Deny'."), ValueMap{"Grant","Deny"}, Values{"Grant","Deny"}] String PermissionState; + [Required, Description("The set of permissions for the SQL database.")] String Permissions[]; + [Key, Description("The host name of the SQL Server to be configured.")] String SQLServer; + [Key, Description("The name of the SQL instance to be configured.")] String SQLInstanceName; +}; diff --git a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.psm1 b/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.psm1 deleted file mode 100644 index 6645d6ccc..000000000 --- a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.psm1 +++ /dev/null @@ -1,201 +0,0 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Debug -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - -# DSC resource to manage SQL database permissions - -# NOTE: This resource requires WMF5 and PsDscRunAsCredential - -function Get-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Database, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [System.String] - $SQLServer = $env:COMPUTERNAME, - - [System.String] - $SQLInstanceName = "MSSQLSERVER" - ) - - if(!$SQL) - { - $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - } - - if($SQL) - { - # Check database exists - if(!($SQLDatabase = $SQL.Databases[$Database])) - { - throw New-TerminatingError -ErrorType NoDatabase -FormatArgs @($Database,$SQLServer,$SQLInstanceName) -ErrorCategory InvalidResult - } - - # Check login exists - if(!($SQLLogin = $SQL.Logins[$Name])) - { - throw New-TerminatingError -ErrorType LoginNotFound -FormatArgs @($Name,$SQLServer,$SQLInstanceName) -ErrorCategory ObjectNotFound - } - - $Permissions = @() - $PermissionSet = $SQLDatabase.EnumDatabasePermissions($Name) - foreach($Permission in $PermissionSet) - { - $Properties = ($Permission.PermissionType | Get-Member -MemberType Property).Name - foreach($Property in $Properties) - { - if($Permission.PermissionType."$Property") - { - $Permissions += $Property - } - } - } - } - else - { - $Name = $null - } - - $returnValue = @{ - Database = $Database - Name = $Name - Permissions = $Permissions - SQLServer = $SQLServer - SQLInstanceName = $SQLInstanceName - } - - $returnValue -} - - -function Set-TargetResource -{ - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Database, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [System.String] - $SQLServer = $env:COMPUTERNAME, - - [System.String] - $SQLInstanceName = "MSSQLSERVER" - ) - - if(!$SQL) - { - $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - } - - if($SQL) - { - $SQLDatabase = $SQL.Databases[$Database] - - if(!$SQLDatabase.Users[$Name]) - { - try - { - Write-Verbose "Adding SQL login $Name as a user of database $Database on $SQLServer\$SQLInstanceName" - $SQLDatabaseUser = New-Object Microsoft.SqlServer.Management.Smo.User $SQLDatabase,$Name - $SQLDatabaseUser.Login = $Name - $SQLDatabaseUser.Create() - } - catch - { - Write-Verbose "Failed adding SQL login $Name as a user of database $Database on $SQLServer\$SQLInstanceName" - } - } - - if($SQLDatabase.Users[$Name]) - { - try - { - Write-Verbose "Granting SQL login $Name to permissions $Permissions on database $Database on $SQLServer\$SQLInstanceName" - $PermissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet - foreach($Permission in $Permissions) - { - $PermissionSet."$Permission" = $true - } - $SQLDatabase.Grant($PermissionSet,$Name) - } - catch - { - Write-Verbose "Failed granting SQL login $Name to permissions $Permissions on database $Database on $SQLServer\$SQLInstanceName" - } - } - } - - if(!(Test-TargetResource @PSBoundParameters)) - { - throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult - } -} - - -function Test-TargetResource -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Database, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [parameter(Mandatory = $true)] - [System.String[]] - $Permissions, - - [System.String] - $SQLServer = $env:COMPUTERNAME, - - [System.String] - $SQLInstanceName = "MSSQLSERVER" - ) - - $SQLDatabasePermissions = (Get-TargetResource @PSBoundParameters).Permissions - - $result = $true - foreach($Permission in $Permissions) - { - if($SQLDatabasePermissions -notcontains $Permission) - { - Write-Verbose "Failed test for permission $Permission" - $result = $false - } - } - - $result -} - - -Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.schema.mof b/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.schema.mof deleted file mode 100644 index 0b2d24a1f..000000000 --- a/DSCResources/MSFT_xSQLServerDatabasePermissions/MSFT_xSQLServerDatabasePermissions.schema.mof +++ /dev/null @@ -1,9 +0,0 @@ -[ClassVersion("1.0.0.0"), FriendlyName("xSQLServerDatabasePermissions")] -class MSFT_xSQLServerDatabasePermissions : OMI_BaseResource -{ - [Key, Description("The SQL database.")] String Database; - [Required, Description("The name of the SQL login for the permission set.")] String Name; - [Required, Description("The set of permissions for the SQL database.")] String Permissions[]; - [Write, Description("The SQL Server for the database.")] String SQLServer; - [Write, Description("The SQL instance for the database.")] String SQLInstanceName; -}; diff --git a/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 new file mode 100644 index 000000000..4b9d90cde --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 @@ -0,0 +1,185 @@ +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Force + +<# + .SYNOPSIS + This function gets all Key properties defined in the resource schema file + + .PARAMETER Database + This is the SQL database + + .PARAMETER RecoveryModel + This is the RecoveryModel of the SQL database + + .PARAMETER SQLServer + This is a the SQL Server for the database + + .PARAMETER SQLInstanceName + This is a the SQL instance for the database +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sql) + { + Write-Verbose -Message "Getting RecoveryModel of SQL database '$Name'" + $sqlDatabase = $sql.Databases + + if ($sqlDatabase) + { + if ($sqlDatabase[$Name]) + { + $getSqlDatabaseRecoveryModel = Get-SqlDatabaseRecoveryModel -SqlServerObject $sql -DatabaseName $Name + New-VerboseMessage -Message "RecoveryModel of SQL Database name $Name is $getSqlDatabaseRecoveryModel" + } + else + { + New-VerboseMessage -Message "SQL Database name $Name does not exist" + $null = $getSqlDatabaseRecoveryModel + } + } + else + { + New-WarningMessage -Message 'Failed getting SQL databases' + $null = $getSqlDatabaseRecoveryModel + } + } + + $returnValue = @{ + Name = $Name + RecoveryModel = $getSqlDatabaseRecoveryModel + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + } + + $returnValue +} + +<# + .SYNOPSIS + This function gets all Key properties defined in the resource schema file + + .PARAMETER Database + This is the SQL database + + .PARAMETER RecoveryModel + This is the RecoveryModel of the SQL database + + .PARAMETER SQLServer + This is a the SQL Server for the database + + .PARAMETER SQLInstanceName + This is a the SQL instance for the database +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sql) + { + $sqlDatabase = $Sql.Databases[$Name] + if ($sqlDatabase) + { + Write-Verbose -Message "Setting database '$Name' with RecoveryModel '$RecoveryModel'" + Set-SqlDatabaseRecoveryModel -SqlServerObject $sql -DatabaseName $Name -RecoveryModel $RecoveryModel + } + else + { + New-VerboseMessage -Message "SQL Database name $Name does not exist" + } + } +} + +<# + .SYNOPSIS + This function gets all Key properties defined in the resource schema file + + .PARAMETER Database + This is the SQL database + + .PARAMETER RecoveryModel + This is the RecoveryModel of the SQL database + + .PARAMETER SQLServer + This is a the SQL Server for the database + + .PARAMETER SQLInstanceName + This is a the SQL instance for the database +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [parameter(Mandatory = $true)] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel, + + [parameter(Mandatory = $true)] + [System.String] + $SQLServer, + + [parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + Write-Verbose -Message "Testing RecoveryModel of database '$Name'" + + $currentValues = Get-TargetResource @PSBoundParameters + + return Test-SQLDscParameterState -CurrentValues $CurrentValues ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @('Name','RecoveryModel') +} + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.schema.mof b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.schema.mof new file mode 100644 index 000000000..0ffb09a92 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.schema.mof @@ -0,0 +1,8 @@ +[ClassVersion("1.0"), FriendlyName("xSQLServerDatabaseRecoveryModel")] +class MSFT_xSQLServerDatabaseRecoveryModel : OMI_BaseResource +{ + [Key, Description("The SQL database name")] String Name; + [Required, Description("The recovery model to use for the database."), ValueMap{"Full","Simple","BulkLogged"}, Values{"Full","Simple","BulkLogged"}] String RecoveryModel; + [Key, Description("The host name of the SQL Server to be configured.")] String SQLServer; + [Key, Description("The name of the SQL instance to be configured.")] String SQLInstanceName; +}; diff --git a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 index 8ea6698c5..cca1c76fb 100644 --- a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 +++ b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 @@ -1,334 +1,623 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Debug -Message "CurrentPath: $currentPath" +$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 'xSQLServerHelper.psm1') -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop +<# + .SYNOPSIS + Returns the current state of the firewall rules. + .PARAMETER SourcePath + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. + + .PARAMETER Features + One or more SQL feature to create default firewall rules for. Each feature should be seperated with a comma, i.e. 'SQLEngine,IS,RS'. + + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter `SourcePath`. + + .PARAMETER InstanceName + Name of the instance to get firewall rules for. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( + [Parameter()] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", - - [System.String] - $SourceFolder = "Source", + $SourcePath, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Features, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $InstanceName + $InstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential ) $InstanceName = $InstanceName.ToUpper() - Import-Module $PSScriptRoot\..\..\xPDT.psm1 + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) + + if ($SourceCredential) + { + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } + + $null = New-SmbMapping @newSmbMappingParameters + } + + $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' + + New-VerboseMessage -Message "Using path: $pathToSetupExecutable" + + $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable - $Path = Join-Path -Path (Join-Path -Path $SourcePath -ChildPath $SourceFolder) -ChildPath "setup.exe" - $Path = ResolvePath $Path - $SQLVersion = GetSQLVersion -Path $Path - - if($InstanceName -eq "MSSQLSERVER") + if ($SourceCredential) { - $DBServiceName = "MSSQLSERVER" - $AgtServiceName = "SQLSERVERAGENT" - $FTServiceName = "MSSQLFDLauncher" - $RSServiceName = "ReportServer" - $ASServiceName = "MSSQLServerOLAPService" + Remove-SmbMapping -RemotePath $SourcePath -Force + } + + if($InstanceName -eq 'MSSQLSERVER') + { + $databaseServiceName = 'MSSQLSERVER' + $reportServiceName = 'ReportServer' + $analysisServiceName = 'MSSQLServerOLAPService' } else { - $DBServiceName = "MSSQL`$$InstanceName" - $AgtServiceName = "SQLAgent`$$InstanceName" - $FTServiceName = "MSSQLFDLauncher`$$InstanceName" - $RSServiceName = "ReportServer`$$InstanceName" - $ASServiceName = "MSOLAP`$$InstanceName" + $databaseServiceName = 'MSSQL${0}' -f $InstanceName + $reportServiceName = 'ReportServer${0}' -f $InstanceName + $analysisServiceName = 'MSOLAP${0}' -f $InstanceName } - $ISServiceName = "MsDtsServer" + $SQLVersion + "0" - - $Ensure = "Present" - $Services = Get-Service - $FeaturesInstalled = "" - foreach($Feature in $Features.Split(",")) + + $integrationServiceName = 'MsDtsServer{0}0' -f $sqlVersion + $browserServiceName = 'SQLBrowser' + + $ensure = 'Absent' + $featuresInstalled = '' + + $services = Get-Service + + foreach ($currentFeature in $Features.Split(',')) { - switch($Feature) + switch ($currentFeature) { - "SQLENGINE" + 'SQLENGINE' { - if($Services | Where-Object {$_.Name -eq $DBServiceName}) + if ($services | Where-Object {$_.Name -eq $databaseServiceName}) { - $FeaturesInstalled += "SQLENGINE," - if(Get-FirewallRule -DisplayName ("SQL Server Database Engine instance " + $InstanceName) -Application ((GetSQLPath -Feature "SQLENGINE" -InstanceName $InstanceName) + "\sqlservr.exe")) + $featuresInstalled += "$_," + + $pathToDatabaseEngineExecutable = Join-Path -Path (Get-SQLPath -Feature $_ -InstanceName $InstanceName) -ChildPath 'sqlservr.exe' + $databaseEngineFirewallRuleDisplayName = "SQL Server Database Engine instance $InstanceName" + + $databaseEngineFirewallRuleParameters = @{ + DisplayName = $databaseEngineFirewallRuleDisplayName + Program = $pathToDatabaseEngineExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @databaseEngineFirewallRuleParameters) { - $DatabaseEngineFirewall = $true + $databaseEngineFirewall = $true + $ensure = 'Present' } else { - $DatabaseEngineFirewall = $false - $Ensure = "Absent" + $databaseEngineFirewall = $false } - if(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser") + + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters) { - $BrowserFirewall = $true + $browserFirewall = $true + $ensure = 'Present' } else { - $BrowserFirewall = $false - $Ensure = "Absent" + $browserFirewall = $false } } } - "RS" + + 'RS' { - if($Services | Where-Object {$_.Name -eq $RSServiceName}) + if ($services | Where-Object {$_.Name -eq $reportServiceName}) { - $FeaturesInstalled += "RS," - if((Get-FirewallRule -DisplayName "SQL Server Reporting Services 80" -Port "TCP/80") -and (Get-FirewallRule -DisplayName "SQL Server Reporting Services 443" -Port "TCP/443")) + $featuresInstalled += "$_," + + $reportingServicesNoSslProtocol = 'TCP' + $reportingServicesNoSslLocalPort = '80' + $reportingServicesNoSslFirewallRuleDisplayName = 'SQL Server Reporting Services 80' + + $reportingServicesNoSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesNoSslFirewallRuleDisplayName + Protocol = $reportingServicesNoSslProtocol + LocalPort = $reportingServicesNoSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + $reportingServicesSslProtocol = 'TCP' + $reportingServicesSslLocalPort = '443' + $reportingServicesSslFirewallRuleDisplayName = 'SQL Server Reporting Services 443' + + $reportingServicesSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesSslFirewallRuleDisplayName + Protocol = $reportingServicesSslProtocol + LocalPort = $reportingServicesSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if ((Test-IsFirewallRuleInDesiredState @reportingServicesNoSslFirewallRuleParameters) ` + -and (Test-IsFirewallRuleInDesiredState @reportingServicesSslFirewallRuleParameters)) { - $ReportingServicesFirewall = $true + $reportingServicesFirewall = $true + $ensure = 'Present' } else { - $ReportingServicesFirewall = $false - $Ensure = "Absent" + $reportingServicesFirewall = $false } } } - "AS" + + 'AS' { - if($Services | Where-Object {$_.Name -eq $ASServiceName}) + if ($services | Where-Object {$_.Name -eq $analysisServiceName}) { - $FeaturesInstalled += "AS," - if(Get-FirewallRule -DisplayName "SQL Server Analysis Services instance $InstanceName" -Service $ASServiceName) + $featuresInstalled += "$_," + + $analysisServicesFirewallRuleDisplayName = "SQL Server Analysis Services instance $InstanceName" + + $analysisServicesFirewallRuleParameters = @{ + DisplayName = $analysisServicesFirewallRuleDisplayName + Service = $analysisServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @analysisServicesFirewallRuleParameters) { - $AnalysisServicesFirewall = $true + $analysisServicesFirewall = $true + $ensure = 'Present' } else { - $AnalysisServicesFirewall = $false - $Ensure = "Absent" + $analysisServicesFirewall = $false } - if(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser") + + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters) { - $BrowserFirewall = $true + $browserFirewall = $true + $ensure = 'Present' } else { - $BrowserFirewall = $false - $Ensure = "Absent" + $browserFirewall = $false } } } - "IS" + + 'IS' { - if($Services | Where-Object {$_.Name -eq $ISServiceName}) + if ($services | Where-Object {$_.Name -eq $integrationServiceName}) { - $FeaturesInstalled += "IS," - if((Get-FirewallRule -DisplayName "SQL Server Integration Services Application" -Application ((GetSQLPath -Feature "IS" -SQLVersion $SQLVersion) + "Binn\MsDtsSrvr.exe")) -and (Get-FirewallRule -DisplayName "SQL Server Integration Services Port" -Port "TCP/135")) + $featuresInstalled += "$_," + + $integrationServicesRuleApplicationDisplayName = 'SQL Server Integration Services Application' + $pathToIntegrationServicesExecutable = (Join-Path -Path (Join-Path -Path (Get-SQLPath -Feature 'IS' -SQLVersion $sqlVersion) -ChildPath 'Binn') -ChildPath 'MsDtsSrvr.exe') + + $integrationServicesFirewallRuleApplicationParameters = @{ + DisplayName = $integrationServicesRuleApplicationDisplayName + Program = $pathToIntegrationServicesExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + $integrationServicesProtocol = 'TCP' + $integrationServicesLocalPort = '135' + $integrationServicesFirewallRuleDisplayName = 'SQL Server Integration Services Port' + + $integrationServicesFirewallRulePortParameters = @{ + DisplayName = $integrationServicesFirewallRuleDisplayName + Protocol = $integrationServicesProtocol + LocalPort = $integrationServicesLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if ((Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRuleApplicationParameters) ` + -and (Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRulePortParameters)) { - $IntegrationServicesFirewall = $true + $integrationServicesFirewall = $true + $ensure = 'Present' } else { - $IntegrationServicesFirewall = $false - $Ensure = "Absent" + $integrationServicesFirewall = $false } } } } } - $FeaturesInstalled = $FeaturesInstalled.Trim(",") - $returnValue = @{ - Ensure = $Ensure + $featuresInstalled = $featuresInstalled.Trim(',') + + return @{ + Ensure = $ensure SourcePath = $SourcePath - SourceFolder = $SourceFolder - Features = $FeaturesInstalled + Features = $featuresInstalled InstanceName = $InstanceName - DatabaseEngineFirewall = $DatabaseEngineFirewall - BrowserFirewall = $BrowserFirewall - ReportingServicesFirewall = $ReportingServicesFirewall - AnalysisServicesFirewall = $AnalysisServicesFirewall - IntegrationServicesFirewall = $IntegrationServicesFirewall + DatabaseEngineFirewall = $databaseEngineFirewall + BrowserFirewall = $browserFirewall + ReportingServicesFirewall = $reportingServicesFirewall + AnalysisServicesFirewall = $analysisServicesFirewall + IntegrationServicesFirewall = $integrationServicesFirewall } - - $returnValue } +<# + .SYNOPSIS + Creates, updates or remove the firewall rules. + + .PARAMETER Ensure + If the firewall rules should be present ('Present') or absent ('Absent'). The default value is 'Present'. + + .PARAMETER SourcePath + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. + .PARAMETER Features + One or more SQL feature to create default firewall rules for. Each feature should be seperated with a comma, i.e. 'SQLEngine,IS,RS'. + + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter `SourcePath`. + + .PARAMETER InstanceName + Name of the instance to get firewall rules for. +#> function Set-TargetResource { [CmdletBinding()] param ( - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', + [Parameter()] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", + $SourcePath, - [System.String] - $SourceFolder = "Source", - - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Features, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $InstanceName + $InstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential ) $InstanceName = $InstanceName.ToUpper() - Import-Module $PSScriptRoot\..\..\xPDT.psm1 + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) + + if ($SourceCredential) + { + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } + + $null = New-SmbMapping @newSmbMappingParameters + } + + $path = Join-Path -Path $SourcePath -ChildPath 'setup.exe' + + New-VerboseMessage -Message "Using path: $path" - $Path = Join-Path -Path (Join-Path -Path $SourcePath -ChildPath $SourceFolder) -ChildPath "setup.exe" - $Path = ResolvePath $Path - $SQLVersion = GetSQLVersion -Path $Path + $sqlVersion = Get-SqlMajorVersion -Path $path + + if ($SourceCredential) + { + Remove-SmbMapping -RemotePath $SourcePath -Force + } - if($InstanceName -eq "MSSQLSERVER") + if ($InstanceName -eq 'MSSQLSERVER') { - $DBServiceName = "MSSQLSERVER" - $AgtServiceName = "SQLSERVERAGENT" - $FTServiceName = "MSSQLFDLauncher" - $RSServiceName = "ReportServer" - $ASServiceName = "MSSQLServerOLAPService" + $analysisServiceName = 'MSSQLServerOLAPService' } else { - $DBServiceName = "MSSQL`$$InstanceName" - $AgtServiceName = "SQLAgent`$$InstanceName" - $FTServiceName = "MSSQLFDLauncher`$$InstanceName" - $RSServiceName = "ReportServer`$$InstanceName" - $ASServiceName = "MSOLAP`$$InstanceName" + $analysisServiceName = 'MSOLAP${0}' -f $InstanceName } - $ISServiceName = "MsDtsServer" + $SQLVersion + "0" - - $SQLData = Get-TargetResource -SourcePath $SourcePath -SourceFolder $SourceFolder -Features $Features -InstanceName $InstanceName - foreach($Feature in $SQLData.Features.Split(",")) + $browserServiceName = 'SQLBrowser' + + $getTargetResourceResult = Get-TargetResource -SourcePath $SourcePath -Features $Features -InstanceName $InstanceName + + foreach ($currentFeature in $getTargetResourceResult.Features.Split(',')) { - switch($Feature) + switch ($currentFeature) { - "SQLENGINE" + 'SQLENGINE' { - if(!($SQLData.DatabaseEngineFirewall)){ - if(!(Get-FirewallRule -DisplayName ("SQL Server Database Engine instance " + $InstanceName) -Application ((GetSQLPath -Feature "SQLENGINE" -InstanceName $InstanceName) + "\sqlservr.exe"))) + if (-not ($getTargetResourceResult.DatabaseEngineFirewall)) + { + $pathToDatabaseEngineExecutable = Join-Path -Path (Get-SQLPath -Feature $_ -InstanceName $InstanceName) -ChildPath 'sqlservr.exe' + $databaseEngineFirewallRuleDisplayName = "SQL Server Database Engine instance $InstanceName" + + $databaseEngineFirewallRuleParameters = @{ + DisplayName = $databaseEngineFirewallRuleDisplayName + Program = $pathToDatabaseEngineExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @databaseEngineFirewallRuleParameters)) { - New-FirewallRule -DisplayName ("SQL Server Database Engine instance " + $InstanceName) -Application ((GetSQLPath -Feature "SQLENGINE" -InstanceName $InstanceName) + "\sqlservr.exe") + New-NetFirewallRule @databaseEngineFirewallRuleParameters } } - if(!($SQLData.BrowserFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser")) + + if (-not ($getTargetResourceResult.BrowserFirewall)) + { + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser" + New-NetFirewallRule @browserFirewallRuleParameters } } } - "RS" + + 'RS' { - if(!($SQLData.ReportingServicesFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Reporting Services 80" -Port "TCP/80")) + if (-not ($getTargetResourceResult.ReportingServicesFirewall)) + { + $reportingServicesNoSslProtocol = 'TCP' + $reportingServicesNoSslLocalPort = '80' + $reportingServicesNoSslFirewallRuleDisplayName = 'SQL Server Reporting Services 80' + + $reportingServicesNoSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesNoSslFirewallRuleDisplayName + Protocol = $reportingServicesNoSslProtocol + LocalPort = $reportingServicesNoSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @reportingServicesNoSslFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Reporting Services 80" -Port "TCP/80" + New-NetFirewallRule @reportingServicesNoSslFirewallRuleParameters + } + + $reportingServicesSslProtocol = 'TCP' + $reportingServicesSslLocalPort = '443' + $reportingServicesSslFirewallRuleDisplayName = 'SQL Server Reporting Services 443' + + $reportingServicesSslFirewallRuleParameters = @{ + DisplayName = $reportingServicesSslFirewallRuleDisplayName + Protocol = $reportingServicesSslProtocol + LocalPort = $reportingServicesSslLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' } - if(!(Get-FirewallRule -DisplayName "SQL Server Reporting Services 443" -Port "TCP/443")) + + if (-not (Test-IsFirewallRuleInDesiredState @reportingServicesSslFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Reporting Services 443" -Port "TCP/443" + New-NetFirewallRule @reportingServicesSslFirewallRuleParameters } } } - "AS" + + 'AS' { - if(!($SQLData.AnalysisServicesFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Analysis Services instance $InstanceName" -Service $ASServiceName)) + if (-not ($getTargetResourceResult.AnalysisServicesFirewall)) + { + $analysisServicesFirewallRuleDisplayName = "SQL Server Analysis Services instance $InstanceName" + + $analysisServicesFirewallRuleParameters = @{ + DisplayName = $analysisServicesFirewallRuleDisplayName + Service = $analysisServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if(-not (Test-IsFirewallRuleInDesiredState @analysisServicesFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Analysis Services instance $InstanceName" -Service $ASServiceName + New-NetFirewallRule @analysisServicesFirewallRuleParameters } } - if(!($SQLData.BrowserFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser")) + + if (-not ($getTargetResourceResult.BrowserFirewall)) + { + $browserFirewallRuleDisplayName = 'SQL Server Browser' + + $browserFirewallRuleParameters = @{ + DisplayName = $browserFirewallRuleDisplayName + Service = $browserServiceName + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters)) { - New-FirewallRule -DisplayName "SQL Server Browser" -Service "SQLBrowser" + New-NetFirewallRule @browserFirewallRuleParameters } } } - "IS" + + 'IS' { - if(!($SQLData.IntegrationServicesFirewall)){ - if(!(Get-FirewallRule -DisplayName "SQL Server Integration Services Application" -Application ((GetSQLPath -Feature "IS" -SQLVersion $SQLVersion) + "Binn\MsDtsSrvr.exe"))) + if (!($getTargetResourceResult.IntegrationServicesFirewall)) + { + $integrationServicesRuleApplicationDisplayName = 'SQL Server Integration Services Application' + $pathToIntegrationServicesExecutable = (Join-Path -Path (Join-Path -Path (Get-SQLPath -Feature 'IS' -SQLVersion $sqlVersion) -ChildPath 'Binn') -ChildPath 'MsDtsSrvr.exe') + + $integrationServicesFirewallRuleApplicationParameters = @{ + DisplayName = $integrationServicesRuleApplicationDisplayName + Program = $pathToIntegrationServicesExecutable + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' + } + + if (-not (Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRuleApplicationParameters)) { - New-FirewallRule -DisplayName "SQL Server Integration Services Application" -Application ((GetSQLPath -Feature "IS" -SQLVersion $SQLVersion) + "Binn\MsDtsSrvr.exe") + New-NetFirewallRule @integrationServicesFirewallRuleApplicationParameters + } + + $integrationServicesProtocol = 'TCP' + $integrationServicesLocalPort = '135' + $integrationServicesFirewallRuleDisplayName = 'SQL Server Integration Services Port' + + $integrationServicesFirewallRulePortParameters = @{ + DisplayName = $integrationServicesFirewallRuleDisplayName + Protocol = $integrationServicesProtocol + LocalPort = $integrationServicesLocalPort + Enabled = 'True' + Profile = 'Any' + Direction = 'Inbound' } - if(!(Get-FirewallRule -DisplayName "SQL Server Integration Services Port" -Port "TCP/135")) + + if (-not (Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRulePortParameters)) { - New-FirewallRule -DisplayName "SQL Server Integration Services Port" -Port "TCP/135" + New-NetFirewallRule @integrationServicesFirewallRulePortParameters } } } } } - if(!(Test-TargetResource -SourcePath $SourcePath -SourceFolder $SourceFolder -Features $Features -InstanceName $InstanceName)) + if (-not (Test-TargetResource -SourcePath $SourcePath -Features $Features -InstanceName $InstanceName)) { throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult } } +<# + .SYNOPSIS + Test if the firewall rules are in desired state. + + .PARAMETER Ensure + If the firewall rules should be present ('Present') or absent ('Absent'). The default value is 'Present'. + + .PARAMETER SourcePath + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. + + .PARAMETER Features + One or more SQL feature to create default firewall rules for. Each feature should be seperated with a comma, i.e. 'SQLEngine,IS,RS'. + .PARAMETER SourceCredential + Credentials used to access the path set in the parameter `SourcePath`. + + .PARAMETER InstanceName + Name of the instance to get firewall rules for. +#> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( - [ValidateSet("Present","Absent")] - [System.String] - $Ensure = "Present", - + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", + $Ensure = 'Present', + [Parameter()] [System.String] - $SourceFolder = "Source", + $SourcePath, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Features, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $InstanceName + $InstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $SourceCredential ) - $result = ((Get-TargetResource -SourcePath $SourcePath -SourceFolder $SourceFolder -Features $Features -InstanceName $InstanceName).Ensure -eq $Ensure) - - $result -} + $getTargetResourceResult = Get-TargetResource -SourcePath $SourcePath -Features $Features -InstanceName $InstanceName + return ($getTargetResourceResult.Ensure -eq $Ensure) +} -function GetSQLVersion -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory=$true)] - [String] - $Path - ) +<# + .SYNOPSIS + Get the path to SQL Server executables. - return (Get-Item -Path $Path).VersionInfo.ProductVersion.Split(".")[0] -} + .PARAMETER Feature + String containing the feature name for which to get the path. + .PARAMETER InstanceName + String containing the name of the instance for which to get the path. -function GetSQLPath + .PARAMETER SQLVersion + String containing the major version of the SQL server to get the path for. + This is used to evaluate the Integration Services version number. +#> +function Get-SQLPath { [CmdletBinding()] param @@ -336,42 +625,75 @@ function GetSQLPath [Parameter(Mandatory=$true)] [String] $Feature, - + + [Parameter()] [String] $InstanceName, + [Parameter()] [String] $SQLVersion ) - if(($Feature -eq "SQLENGINE") -or ($Feature -eq "AS")) + if (($Feature -eq 'SQLENGINE') -or ($Feature -eq 'AS')) { - switch($Feature) + switch ($Feature) { - "SQLENGINE" + 'SQLENGINE' { - $RegSubKey = "SQL" + $productinstanceId = 'SQL' } - "AS" + + 'AS' { - $RegSubKey = "OLAP" + $productinstanceId = 'OLAP' } } - $RegKey = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\$RegSubKey" -Name $InstanceName).$InstanceName - $Path = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$RegKey\setup" -Name "SQLBinRoot")."SQLBinRoot" + + $instanceId = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\$($productinstanceId)" -Name $InstanceName).$InstanceName + $path = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($instanceId)\setup" -Name 'SQLBinRoot').SQLBinRoot } - if($Feature -eq "IS") + if ($Feature -eq 'IS') { - $Path = (Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\" + $SQLVersion + "0\DTS\setup") -Name "SQLPath")."SQLPath" + $path = (Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($SQLVersion)0\DTS\setup") -Name 'SQLPath').SQLPath } - return $Path + return $path } +<# + .SYNOPSIS + Evaluates if the firewall rule is in desired state. + + .PARAMETER DisplayName + String containing the display name for the firewall rule. + + .PARAMETER Enabled + String containing either 'True' or 'False' meaning if the firewall rule should be active or not. + + .PARAMETER Profile + String containing one or more profiles to which the firewall rule is assigned. + + .PARAMETER Direction + String containing the direction of traffic for the the firewall rule. It can be either 'Inbound' or 'Outbound'. + + .PARAMETER Program + String containing the path to an executable. This paramater is optional. + + .PARAMETER Service + String containing the name of a service for the firewall rule. This parameter is optional. + + .PARAMETER Protocol + String containing the protocol for the local port parameter. This parameter is optional. -function Get-FirewallRule + .PARAMETER LocalPort + String containing the local port for the firewall rule. This parameter is optional, with the exception that if + the parameter Protocol is specified this parameter must also be specified. +#> +function Test-IsFirewallRuleInDesiredState { + [OutputType([System.Boolean])] [CmdletBinding()] param ( @@ -379,89 +701,101 @@ function Get-FirewallRule [String] $DisplayName, + [Parameter(Mandatory=$true)] + [ValidateSet('True','False')] + [String] + $Enabled, + + [Parameter(Mandatory=$true)] + [String[]] + [ValidateSet('Any','Domain','Private','Public','NotApplicable')] + $Profile, + + [Parameter(Mandatory=$true)] + [ValidateSet('Inbound','Outbound')] [String] - $Application, + $Direction, + [Parameter()] + [String] + $Program, + + [Parameter()] [String] $Service, + [Parameter()] + [ValidateSet('TCP','UDP','ICMPv4','ICMPv6')] + [String] + $Protocol, + + [Parameter()] [String] - $Port + $LocalPort ) - $Return = $false - if($FirewallRule = Get-NetFirewallRule -DisplayName $DisplayName -ErrorAction SilentlyContinue) + $isRuleInDesiredState = $false + + if ($firewallRule = Get-NetFirewallRule -DisplayName $DisplayName -ErrorAction SilentlyContinue) { - if(($FirewallRule.Enabled) -and ($FirewallRule.Profile -eq "Any") -and ($FirewallRule.Direction -eq "Inbound")) + if (($firewallRule.Enabled -eq $Enabled) -and ($firewallRule.Profile -eq $Profile) -and ($firewallRule.Direction -eq $Direction)) { - if($PSBoundParameters.ContainsKey("Application")) + if ($PSBoundParameters.ContainsKey('Program')) { - if($FirewallApplicationFilter = Get-NetFirewallApplicationFilter -AssociatedNetFirewallRule $FirewallRule -ErrorAction SilentlyContinue) + if ($firewallApplicationFilter = Get-NetFirewallApplicationFilter -AssociatedNetFirewallRule $firewallRule -ErrorAction SilentlyContinue) { - if($FirewallApplicationFilter.Program -eq $Application) + if ($firewallApplicationFilter.Program -eq $Program) { - $Return = $true + $isRuleInDesiredState = $true } } } - if($PSBoundParameters.ContainsKey("Service")) + + if ($PSBoundParameters.ContainsKey('Service')) { - if($FirewallServiceFilter = Get-NetFirewallServiceFilter -AssociatedNetFirewallRule $FirewallRule -ErrorAction SilentlyContinue) + if ($firewallServiceFilter = Get-NetFirewallServiceFilter -AssociatedNetFirewallRule $firewallRule -ErrorAction SilentlyContinue) { - if($FirewallServiceFilter.Service -eq $Service) + if ($firewallServiceFilter.Service -eq $Service) { - $Return = $true + $isRuleInDesiredState = $true } } } - if($PSBoundParameters.ContainsKey("Port")) + + if ($PSBoundParameters.ContainsKey('Protocol') -and $PSBoundParameters.ContainsKey('LocalPort')) { - if($FirewallPortFilter = Get-NetFirewallPortFilter -AssociatedNetFirewallRule $FirewallRule -ErrorAction SilentlyContinue) + if ($firewallPortFilter = Get-NetFirewallPortFilter -AssociatedNetFirewallRule $firewallRule -ErrorAction SilentlyContinue) { - if(($FirewallPortFilter.Protocol -eq $Port.Split("/")[0]) -and ($FirewallPortFilter.LocalPort -eq $Port.Split("/")[1])) + if ($firewallPortFilter.Protocol -eq $Protocol -and $firewallPortFilter.LocalPort -eq $LocalPort) { - $Return = $true + $isRuleInDesiredState = $true } } } } } - return $Return + + return $isRuleInDesiredState } +<# + .SYNOPSIS + Returns the SQL Server major version from the setup.exe executable provided in the Path parameter. -function New-FirewallRule + .PARAMETER Path + String containing the path to the SQL Server setup.exe executable. +#> +function Get-SqlMajorVersion { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String] - $DisplayName, - - [String] - $Application, - - [String] - $Service, - - [String] - $Port + $path ) - if($PSBoundParameters.ContainsKey("Application")) - { - New-NetFirewallRule -DisplayName $DisplayName -Enabled True -Profile Any -Direction Inbound -Program $Application - } - if($PSBoundParameters.ContainsKey("Service")) - { - New-NetFirewallRule -DisplayName $DisplayName -Enabled True -Profile Any -Direction Inbound -Service $Service - } - if($PSBoundParameters.ContainsKey("Port")) - { - New-NetFirewallRule -DisplayName $DisplayName -Enabled True -Profile Any -Direction Inbound -Protocol $Port.Split("/")[0] -LocalPort $Port.Split("/")[1] - } + (Get-Item -Path $path).VersionInfo.ProductVersion.Split('.')[0] } - Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof index ccb122cb5..8879b0459 100644 --- a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof +++ b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.schema.mof @@ -3,7 +3,6 @@ class MSFT_xSQLServerFirewall : OMI_BaseResource { [Write, Description("An enumerated value that describes if the SQL firewall rules are is expected to be enabled on the machine.\nPresent {default} \nAbsent \n"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Write, Description("UNC path to the root of the source files for installation.")] String SourcePath; - [Write, Description("Folder within the source path containing the source files for installation.")] String SourceFolder; [Key, Description("SQL features to enable firewall rules for.")] String Features; [Key, Description("SQL instance to enable firewall rules for.")] String InstanceName; [Read, Description("Is the firewall rule for the Database Engine enabled?")] boolean DatabaseEngineFirewall; @@ -11,4 +10,5 @@ class MSFT_xSQLServerFirewall : OMI_BaseResource [Read, Description("Is the firewall rule for Reporting Services enabled?")] boolean ReportingServicesFirewall; [Read, Description("Is the firewall rule for Analysis Services enabled?")] boolean AnalysisServicesFirewall; [Read, Description("Is the firewall rule for the Integration Services enabled?")] boolean IntegrationServicesFirewall; + [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath'.")] String SourceCredential; }; diff --git a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 index e603dabcf..3bbd95ae1 100644 --- a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 +++ b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 @@ -1,236 +1,539 @@ -$script:currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Debug -Message "CurrentPath: $script:currentPath" +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force -# Load helper functions -Import-Module $script:currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop +<# + .SYNOPSIS + Gets the specified login by name. + .PARAMETER Name + The name of the login to retrieve. + + .PARAMETER SQLServer + Hostname of the SQL Server to retrieve the login from. + + .PARAMETER SQLInstanceName + Name of the SQL instance to retrieve the login from. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure, - - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [System.Management.Automation.PSCredential] - $LoginCredential, - - [ValidateSet('SqlLogin', 'WindowsUser', 'WindowsGroup')] - [System.String] - $LoginType, - - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLServer, - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLInstanceName = 'MSSQLSERVER' + $SQLInstanceName ) + + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + Write-Verbose 'Getting SQL logins' + New-VerboseMessage -Message "Getting the login '$Name' from '$SQLServer\$SQLInstanceName'" - if ($sql) - { - Write-Verbose 'Getting SQL logins' + $login = $serverObject.Logins[$Name] - $sqlLogins = $sql.Logins - if ($sqlLogins) - { - if ($sqlLogins[$Name]) - { - Write-Verbose "SQL login name $Name is present" - $Ensure = 'Present' - $LoginType = $sqlLogins[$Name].LoginType - Write-Verbose "SQL login name is of type $LoginType" - } - else - { - Write-Verbose "SQL login name $Name is absent" - $Ensure = 'Absent' - } - } - else - { - Write-Verbose 'Failed getting SQL logins' - $Ensure = 'Absent' - } + if ( $login ) + { + $Ensure = 'Present' } else { $Ensure = 'Absent' } + New-VerboseMessage -Message "The login '$Name' is $ensure from the '$SQLServer\$SQLInstanceName' instance." + $returnValue = @{ Ensure = $Ensure Name = $Name - LoginType = $LoginType + LoginType = $login.LoginType SQLServer = $SQLServer SQLInstanceName = $SQLInstanceName } - $returnValue + if ( $login.LoginType -eq 'SqlLogin' ) + { + $returnValue.Add('LoginMustChangePassword',$login.MustChangePassword) + $returnValue.Add('LoginPasswordExpirationEnabled',$login.PasswordExpirationEnabled) + $returnValue.Add('LoginPasswordPolicyEnforced',$login.PasswordPolicyEnforced) + } + + return $returnValue } +<# + .SYNOPSIS + Creates a login. + + .PARAMETER Ensure + Specifies if the login to exist. Default is 'Present'. + + .PARAMETER Name + The name of the login to retrieve. + + .PARAMETER LoginType + The type of login to create. Default is 'WindowsUser' + + .PARAMETER SQLServer + Hostname of the SQL Server to create the login on. + + .PARAMETER SQLInstanceName + Name of the SQL instance to create the login on. + + .PARAMETER LoginCredential + The credential containing the password for a SQL Login. Only applies if the login type is SqlLogin. + + .PARAMETER LoginMustChangePassword + Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordExpirationEnabled + Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordPolicyEnforced + Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. +#> function Set-TargetResource { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [System.Management.Automation.PSCredential] - $LoginCredential, - - [ValidateSet('SqlLogin', 'WindowsUser', 'WindowsGroup')] + [Parameter()] + [ValidateSet( + 'WindowsUser', + 'WindowsGroup', + 'SqlLogin', + 'Certificate', + 'AsymmetricKey', + 'ExternalUser', + 'ExternalGroup' + )] [System.String] $LoginType = 'WindowsUser', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLServer, - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLInstanceName = 'MSSQLSERVER' - ) + $SQLInstanceName, - if ( ($Ensure -eq 'Present') -and - ($LoginType -eq 'SqlLogin') -and - !$PSBoundParameters.ContainsKey('LoginCredential') ) - { - throw New-TerminatingError -ErrorType FailedLogin - } + [Parameter()] + [System.Management.Automation.PSCredential] + $LoginCredential, + + [Parameter()] + [bool] + $LoginMustChangePassword = $true, - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + [Parameter()] + [bool] + $LoginPasswordExpirationEnabled = $true, - if ($sql) + [Parameter()] + [bool] + $LoginPasswordPolicyEnforced = $true + ) + + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + switch ( $Ensure ) { - switch ($Ensure) + 'Present' { - 'Present' + if ( $serverObject.Logins[$Name] ) { - try - { - Write-Verbose "Creating SQL login $Name of type $LoginType" + $login = $serverObject.Logins[$Name] + + if ( $login.LoginType -eq 'SqlLogin' ) + { + + + if ( $login.PasswordExpirationEnabled -ne $LoginPasswordExpirationEnabled ) + { + New-VerboseMessage -Message "Setting PasswordExpirationEnabled to '$LoginPasswordExpirationEnabled' for the login '$Name' on the '$SQLServer\$SQLInstanceName' instance." + $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled + Update-SQLServerLogin -Login $login + } - $sqlLogin = New-Object Microsoft.SqlServer.Management.Smo.Login $sql, $Name - $sqlLogin.LoginType = $LoginType - if ($LoginType -eq 'SqlLogin') + if ( $login.PasswordPolicyEnforced -ne $LoginPasswordPolicyEnforced ) { - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Create login")) ) { - $sqlLogin.Create( $LoginCredential.GetNetworkCredential().Password ) - } + New-VerboseMessage -Message "Setting PasswordPolicyEnforced to '$LoginPasswordPolicyEnforced' for the login '$Name' on the '$SQLServer\$SQLInstanceName' instance." + $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced + Update-SQLServerLogin -Login $login } - else + + # Set the password if it is specified + if ( $LoginCredential ) { - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Create login")) ) { - $sqlLogin.Create() - } + Set-SQLServerLoginPassword -Login $login -SecureString $LoginCredential.Password } } - catch - { - Write-Verbose "Failed creating SQL login $Name of type $LoginType" - - throw $_ - } } - - 'Absent' + else { - try + # Some login types need additional work. These will need to be fleshed out more in the future + if ( @('Certificate','AsymmetricKey','ExternalUser','ExternalGroup') -contains $LoginType ) + { + throw New-TerminatingError -ErrorType LoginTypeNotImplemented -FormatArgs $LoginType -ErrorCategory NotImplemented + } + + if ( ( $LoginType -eq 'SqlLogin' ) -and ( -not $LoginCredential ) ) { - Write-Verbose "Deleting SQL login $Name" + throw New-TerminatingError -ErrorType LoginCredentialNotFound -FormatArgs $Name -ErrorCategory ObjectNotFound + } + + New-VerboseMessage -Message "Adding the login '$Name' to the '$SQLServer\$SQLInstanceName' instance." + + $login = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login -ArgumentList $serverObject,$Name + $login.LoginType = $LoginType - $sqlLogin = $($sql.Logins[$Name]) - if ($sqlLogin) + switch ($LoginType) + { + SqlLogin { - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Remove login")) ) { - Remove-SqlLogin -Login $sqlLogin + # Verify the instance is in Mixed authentication mode + if ( $serverObject.LoginMode -notmatch 'Mixed|Integrated' ) + { + throw New-TerminatingError -ErrorType IncorrectLoginMode -FormatArgs $SQLServer,$SQLInstanceName,$serverObject.LoginMode -ErrorCategory NotImplemented + } + + $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced + $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled + if ( $LoginMustChangePassword ) + { + $LoginCreateOptions = [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::MustChange + } + else + { + $LoginCreateOptions = [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::None } + + New-SQLServerLogin -Login $login -LoginCreateOptions $LoginCreateOptions -SecureString $LoginCredential.Password -ErrorAction Stop + } + + default + { + New-SQLServerLogin -Login $login } - } - catch - { - Write-Verbose "Failed deleting SQL login $Name" - - throw $_ } } } - } - if ( !(Test-TargetResource @PSBoundParameters) ) - { - throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult + 'Absent' + { + if ( $serverObject.Logins[$Name] ) + { + New-VerboseMessage -Message "Dropping the login '$Name' from the '$SQLServer\$SQLInstanceName' instance." + Remove-SQLServerLogin -Login $serverObject.Logins[$Name] + } + } } } +<# + .SYNOPSIS + Tests to verify the login exists and the properties are correctly set. + + .PARAMETER Ensure + Specifies if the login is supposed to exist. Default is 'Present'. + + .PARAMETER Name + The name of the login. + + .PARAMETER LoginType + The type of login. Default is 'WindowsUser' + + .PARAMETER SQLServer + Hostname of the SQL Server. + + .PARAMETER SQLInstanceName + Name of the SQL instance. + + .PARAMETER LoginCredential + The credential containing the password for a SQL Login. Only applies if the login type is SqlLogin. + + .PARAMETER LoginMustChangePassword + Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordExpirationEnabled + Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. + + .PARAMETER LoginPasswordPolicyEnforced + Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. +#> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] $Ensure = 'Present', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] $Name, - [System.Management.Automation.PSCredential] - $LoginCredential, - - [ValidateSet('SqlLogin', 'WindowsUser', 'WindowsGroup')] + [Parameter()] + [ValidateSet('WindowsUser', + 'WindowsGroup', + 'SqlLogin', + 'Certificate', + 'AsymmetricKey', + 'ExternalUser', + 'ExternalGroup' + )] [System.String] $LoginType = 'WindowsUser', - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLServer, - [Parameter(Mandatory)] + [Parameter(Mandatory = $true)] [System.String] - $SQLInstanceName = 'MSSQLSERVER' + $SQLInstanceName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $LoginCredential, + + [Parameter()] + [bool] + $LoginMustChangePassword = $true, + + [Parameter()] + [bool] + $LoginPasswordExpirationEnabled = $true, + + [Parameter()] + [bool] + $LoginPasswordPolicyEnforced = $true ) - $sqlServerLogin = Get-TargetResource @PSBoundParameters + # Assume the test will pass + $testPassed = $true + + $getParams = @{ + Name = $Name + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + } + + $loginInfo = Get-TargetResource @getParams - $result = ($sqlServerLogin.Ensure -eq $Ensure) + if ( $Ensure -ne $loginInfo.Ensure ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' is $($loginInfo.Ensure) rather than $Ensure" + $testPassed = $false + } + + if ( $Ensure -eq 'Present' ) + { + if ( $LoginType -ne $loginInfo.LoginType ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' is a $($loginInfo.LoginType) rather than $LoginType" + $testPassed = $false + } + + if ( $LoginType -eq 'SqlLogin' ) + { + if ( $LoginPasswordExpirationEnabled -ne $loginInfo.LoginPasswordExpirationEnabled ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' has PasswordExpirationEnabled set to $($loginInfo.LoginPasswordExpirationEnabled) rather than $LoginPasswordExpirationEnabled" + $testPassed = $false + } + + if ( $LoginPasswordPolicyEnforced -ne $loginInfo.LoginPasswordPolicyEnforced ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' has PasswordPolicyEnforced set to $($loginInfo.LoginPasswordPolicyEnforced) rather than $LoginPasswordPolicyEnforced" + $testPassed = $false + } + + # If testPassed is still true and a login credential was specified, test the password + if ( $testPassed -and $LoginCredential ) + { + $userCred = [System.Management.Automation.PSCredential]::new($Name, $LoginCredential.Password) + + try + { + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName -SetupCredential $userCred + } + catch + { + New-VerboseMessage -Message "Password validation failed for the login '$Name'." + $testPassed = $false + } + } + } + } - $result + return $testPassed +} + +<# + .SYNOPSIS + Alters a login. + + .PARAMETER Login + The Login object to alter. + + .NOTES + This function allows us to more easily write mocks. +#> +function Update-SQLServerLogin +{ + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Login] + $Login + ) + + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $Login.Alter() + } + catch + { + throw New-TerminatingError -ErrorType AlterLoginFailed -FormatArgs $Login.Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } } <# .SYNOPSIS - Removes a SQL login + Creates a login. .PARAMETER Login - A SQL login of the type Microsoft.SqlServer.Management.Smo.Login + The Login object to create. + + .PARAMETER LoginCreateOptions + The LoginCreateOptions object to use when creating a SQL login. + + .PARAMETER SecureString + The SecureString object that contains the password for a SQL login. .EXAMPLE - $server = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server - $login = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $server, "MyLogin" ) - Remove-SqlLogin -Login $login + CreateLogin -Login $login -LoginCreateOptions $LoginCreateOptions -SecureString $LoginCredential.Password -ErrorAction Stop + + .EXAMPLE + CreateLogin -Login $login + + .NOTES + This function allows us to more easily write mocks. #> -function Remove-SqlLogin +function New-SQLServerLogin +{ + [CmdletBinding(DefaultParameterSetName = 'WindowsLogin')] + param + ( + [Parameter(Mandatory = $true, ParameterSetName = 'WindowsLogin')] + [Parameter(Mandatory = $true, ParameterSetName = 'SqlLogin')] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true, ParameterSetName = 'SqlLogin')] + [Microsoft.SqlServer.Management.Smo.LoginCreateOptions] + $LoginCreateOptions, + + [Parameter(Mandatory = $true, ParameterSetName = 'SqlLogin')] + [System.Security.SecureString] + $SecureString + ) + + switch ( $PSCmdlet.ParameterSetName ) + { + 'SqlLogin' + { + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $login.Create($SecureString,$LoginCreateOptions) + } + catch [Microsoft.SqlServer.Management.Smo.FailedOperationException] + { + if ( $_.Exception.InnerException.InnerException.InnerException -match 'Password validation failed' ) + { + throw New-TerminatingError -ErrorType PasswordValidationFailed -FormatArgs $Name,$_.Exception.InnerException.InnerException.InnerException -ErrorCategory SecurityError + } + else + { + throw New-TerminatingError -ErrorType LoginCreationFailedFailedOperation -FormatArgs $Name -ErrorCategory NotSpecified + } + } + catch + { + throw New-TerminatingError -ErrorType LoginCreationFailedSqlNotSpecified -FormatArgs $Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } + } + + 'WindowsLogin' + { + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $login.Create() + } + catch + { + throw New-TerminatingError -ErrorType LoginCreationFailedWindowsNotSpecified -FormatArgs $Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } + } + } +} + +<# + .SYNOPSIS + Drops a login. + + .PARAMETER Login + The Login object to drop. + + .NOTES + This function allows us to more easily write mocks. +#> +function Remove-SQLServerLogin { - [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] @@ -238,8 +541,74 @@ function Remove-SqlLogin $Login ) - if ( ($PSCmdlet.ShouldProcess($($sqlLogin.Name), "Drop login")) ) { - $sqlLogin.Drop() + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $Login.Drop() + } + catch + { + throw New-TerminatingError -ErrorType DropLoginFailed -FormatArgs $Login.Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference + } +} + +<# + .SYNOPSIS + Changes the password of a SQL Login. + + .PARAMETER Login + The Login object to change the password on. + + .PARAMETER SecureString + The SecureString object that contains the password for a SQL login. + + .NOTES + This function allows us to more easily write mocks. +#> +function Set-SQLServerLoginPassword +{ + param + ( + [Parameter(Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Login] + $Login, + + [Parameter(Mandatory = $true)] + [System.Security.SecureString] + $SecureString + ) + + try + { + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + $Login.ChangePassword($SecureString) + } + catch [Microsoft.SqlServer.Management.Smo.FailedOperationException] + { + if ( $_.Exception.InnerException.InnerException.InnerException -match 'Password validation failed' ) + { + throw New-TerminatingError -ErrorType PasswordValidationFailed -FormatArgs $Name,$_.Exception.InnerException.InnerException.InnerException -ErrorCategory SecurityError + } + else + { + throw New-TerminatingError -ErrorType PasswordChangeFailed -FormatArgs $Name -ErrorCategory NotSpecified + } + } + catch + { + throw New-TerminatingError -ErrorType PasswordChangeFailed -FormatArgs $Name -ErrorCategory NotSpecified + } + finally + { + $ErrorActionPreference = $originalErrorActionPreference } } diff --git a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof index 9fb650958..a8866341f 100644 --- a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof +++ b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof @@ -1,10 +1,15 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerLogin")] class MSFT_xSQLServerLogin : OMI_BaseResource { - [Write, Description("If the values should be present or absent. Valid values are 'Present' or 'Absent'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Key, Description("The name of the SQL login. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name.")] String Name; + [Write, Description("The specified login is Present or Absent. Default is Present."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("The name of the login.")] String Name; + [Write, Description("The type of login to be created. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. Default is WindowsUser. Unsupported login types are Certificate, AsymmetricKey, ExternalUser, and ExternalGroup."), + ValueMap{"WindowsUser","WindowsGroup","SqlLogin","Certificate","AsymmetricKey","ExternalUser","ExternalGroup"}, + Values{"WindowsUser","WindowsGroup","SqlLogin","Certificate","AsymmetricKey","ExternalUser","ExternalGroup"}] String LoginType; + [Key, Description("The hostname of the SQL Server to be configured.")] String SQLServer; + [Key, Description("Name of the SQL instance to be configured.")] String SQLInstanceName; [Write, EmbeddedInstance("MSFT_Credential"), Description("If LoginType is 'SqlLogin' then a PSCredential is needed for the password to the login.")] String LoginCredential; - [Write, Description("The SQL login type. Valid values are 'SqlLogin', 'WindowsUser' or 'WindowsGroup'."), ValueMap{"SqlLogin","WindowsUser","WindowsGroup"}, Values{"SqlLogin","WindowsUser","WindowsGroup"}] String LoginType; - [Key, Description("The SQL Server for the login.")] String SQLServer; - [Key, Description("The SQL instance for the login.")] String SQLInstanceName; + [Write, Description("Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true.")] Boolean LoginMustChangePassword; + [Write, Description("Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true.")] Boolean LoginPasswordExpirationEnabled; + [Write, Description("Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true.")] Boolean LoginPasswordPolicyEnforced; }; diff --git a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 index 9026f0e88..75b23494f 100644 --- a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 +++ b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.psm1 @@ -1,10 +1,39 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Verbose -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - - +$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 'xSQLServerHelper.psm1') + +<# + .SYNOPSIS + Returns the current state of the SQL Server features. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, + use the format ComputerName\InstanceName. + + .PARAMETER SetFilePath + Path to the T-SQL file that will perform Set action. + + .PARAMETER GetFilePath + Path to the T-SQL file that will perform Get action. + Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. + + .PARAMETER TestFilePath + Path to the T-SQL file that will perform Test action. + Any script that does not throw an error or returns null is evaluated to true. + The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. + + .PARAMETER Credential + The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials + to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, + then SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. + Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, + please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). + + .OUTPUTS + Hash table containing key 'GetResult' which holds the value of the result from the SQL script that was ran from the parameter 'GetFilePath'. +#> function Get-TargetResource { [CmdletBinding()] @@ -28,23 +57,24 @@ function Get-TargetResource $TestFilePath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] $Variable - ) + ) $result = Invoke-SqlScript -ServerInstance $ServerInstance -SqlScriptPath $GetFilePath ` -Credential $Credential -Variable $Variable -ErrorAction Stop $getResult = Out-String -InputObject $result - + $returnValue = @{ ServerInstance = [System.String] $ServerInstance SetFilePath = [System.String] $SetFilePath GetFilePath = [System.String] $GetFilePath TestFilePath = [System.String] $TestFilePath - Username = [System.Management.Automation.PSCredential] $Credential + Username = [System.Object] $Credential Variable = [System.String[]] $Variable GetResult = [System.String[]] $getresult } @@ -52,6 +82,36 @@ function Get-TargetResource $returnValue } +<# + .SYNOPSIS + Returns the current state of the SQL Server features. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, + use the format ComputerName\InstanceName. + + .PARAMETER SetFilePath + Path to the T-SQL file that will perform Set action. + + .PARAMETER GetFilePath + Path to the T-SQL file that will perform Get action. + Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. + + .PARAMETER TestFilePath + Path to the T-SQL file that will perform Test action. + Any script that does not throw an error or returns null is evaluated to true. + The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. + + .PARAMETER Credential + The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials + to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, + then SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. + Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, + please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). +#> function Set-TargetResource { [CmdletBinding()] @@ -74,6 +134,7 @@ function Set-TargetResource $TestFilePath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] @@ -84,7 +145,37 @@ function Set-TargetResource -Credential $Credential -Variable $Variable -ErrorAction Stop } +<# + .SYNOPSIS + Returns the current state of the SQL Server features. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, + use the format ComputerName\InstanceName. + .PARAMETER SetFilePath + Path to the T-SQL file that will perform Set action. + + .PARAMETER GetFilePath + Path to the T-SQL file that will perform Get action. + Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. + + .PARAMETER TestFilePath + Path to the T-SQL file that will perform Test action. + Any script that does not throw an error or returns null is evaluated to true. + The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. + + .PARAMETER Credential + The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials + to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, + then SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. + Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, + please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). + +#> function Test-TargetResource { [CmdletBinding()] @@ -108,6 +199,7 @@ function Test-TargetResource $TestFilePath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] @@ -115,11 +207,11 @@ function Test-TargetResource ) try - { + { $result = Invoke-SqlScript -ServerInstance $ServerInstance -SqlScriptPath $TestFilePath ` -Credential $Credential -Variable $Variable -ErrorAction Stop - if($result -eq $null) + if($null -eq $result) { return $true } @@ -135,6 +227,25 @@ function Test-TargetResource } } +<# + .SYNOPSIS + Execute an SQL script located in a file on disk. + + .PARAMETER ServerInstance + The name of an instance of the Database Engine. + For default instances, only specify the computer name. For named instances, use the format ComputerName\InstanceName. + + .PARAMETER SqlScriptPath + Path to SQL script file that will be executed. + + .PARAMETER Credential + The credentials to use to authenticate using SQL Authentication. To authenticate using Windows Authentication, assing the credentials + to the built-in parameter 'PsDscRunAsCredential'. If both parameters 'Credential' and 'PsDscRunAsCredential' are not assigned, then + the SYSTEM account will be used to authenticate using Windows Authentication. + + .PARAMETER Variable + Creates a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. +#> function Invoke-SqlScript { param @@ -148,6 +259,7 @@ function Invoke-SqlScript $SqlScriptPath, [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, [System.String[]] @@ -159,7 +271,7 @@ function Invoke-SqlScript if($null -ne $Credential) { $null = $PSBoundParameters.Add("Username", $Credential.UserName) - $null = $PSBoundParameters.Add("Password", $Credential.GetNetworkCredential().password) + $null = $PSBoundParameters.Add("Password", $Credential.GetNetworkCredential().password) } $null = $PSBoundParameters.Remove("Credential") @@ -169,4 +281,3 @@ function Invoke-SqlScript } Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof index e7c9ab6af..275ccd78c 100644 --- a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof +++ b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof @@ -1,13 +1,11 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerScript")] class MSFT_xSQLServerScript : OMI_BaseResource { - [Required, Description("The name of an instance of the Database Engine. For default instances, only specify the computer name. For named instances, use the format ComputerName\\InstanceName")] String ServerInstance; - [Key, Description("Path to SQL file that will perform Set action.")] String SetFilePath; - [Key, Description("Path to SQL file that will perform Get action.")] String GetFilePath; - [Key, Description("Path to SQL file that will perform Test action.")] String TestFilePath; - [Write, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to run SQL scripts.")] String Credential; - [Write, Description("Creates a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable.")] String Variable[]; - [Read, Description("Result of Get action.")] String GetResult[]; + [Key, Description("The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instance, use the format ComputerName\\InstanceName")] String ServerInstance; + [Key, Description("Path to the T-SQL file that will perform Set action.")] String SetFilePath; + [Key, Description("Path to the T-SQL file that will perform Get action. Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the 'GetResult' property.")] String GetFilePath; + [Key, Description("Path to the T-SQL file that will perform Test action. Any script that does not throw an error or returns null is evaluated to true. The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false.")] String TestFilePath; + [Write, EmbeddedInstance("MSFT_Credential"), Description("The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials to the built-in parameter 'PsDscRunAsCredential'. If both parameters 'Credential' and 'PsDscRunAsCredential' are not assigned, then SYSTEM account will be used to authenticate using Windows Authentication.")] String Credential; + [Write, Description("Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, please go to the help documentation for Invoke-Sqlcmd.")] String Variable[]; + [Read, Description("Contains the values returned from the T-SQL script provided in the parameter 'GetFilePath' when cmdlet Get-DscConfiguration is run.")] String GetResult[]; }; - diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 index 4c0a8148c..438c19e52 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -7,16 +7,19 @@ Import-Module -Name (Join-Path -Path (Split-Path -Path (Split-Path -Path $script Returns the current state of the SQL Server features. .PARAMETER SourcePath - The path to the root of the source files for installation. I.e and UNC path to a shared resource. - - .PARAMETER SourceFolder - Folder within the source path containing the source files for installation. Default value is 'Source'. + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SetupCredential Credential to be used to perform the installation. .PARAMETER SourceCredential - Credential to be used to access SourcePath. + Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy + of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. + For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' + has in the path set, again, by the parameter `SourcePath`. + If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf + folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a + leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER InstanceName Name of the SQL instance to be installed. @@ -29,11 +32,7 @@ function Get-TargetResource ( [Parameter()] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", - - [Parameter()] - [System.String] - $SourceFolder = 'Source', + $SourcePath, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] @@ -50,23 +49,30 @@ function Get-TargetResource $InstanceName = $InstanceName.ToUpper() + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) + if ($SourceCredential) { - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Present' + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } + + $null = New-SmbMapping @newSmbMappingParameters } - - $path = Join-Path -Path (Join-Path -Path $SourcePath -ChildPath $SourceFolder) -ChildPath 'setup.exe' - $path = ResolvePath -Path $path - - New-VerboseMessage -Message "Using path: $path" - $sqlVersion = GetSQLVersion -Path $path + $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' + + New-VerboseMessage -Message "Using path: $pathToSetupExecutable" + + $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable if ($SourceCredential) { - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Absent' + Remove-SmbMapping -RemotePath $SourcePath -Force } - + if ($InstanceName -eq 'MSSQLSERVER') { $databaseServiceName = 'MSSQLSERVER' @@ -83,18 +89,21 @@ function Get-TargetResource $reportServiceName = "ReportServer`$$InstanceName" $analysisServiceName = "MSOLAP`$$InstanceName" } - + $integrationServiceName = "MsDtsServer$($sqlVersion)0" - + $features = '' + $clusteredSqlGroupName = '' + $clusteredSqlHostname = '' + $clusteredSqlIPAddress = '' $services = Get-Service if ($services | Where-Object {$_.Name -eq $databaseServiceName}) { $features += 'SQLENGINE,' - $sqlServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $databaseServiceName}).StartName - $agentServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $agentServiceName}).StartName + $sqlServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$databaseServiceName'").StartName + $agentServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$agentServiceName'").StartName $fullInstanceId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' -Name $InstanceName).$InstanceName @@ -105,7 +114,7 @@ function Get-TargetResource { New-VerboseMessage -Message 'Replication feature detected' $Features += 'REPLICATION,' - } + } else { New-VerboseMessage -Message 'Replication feature not detected' @@ -118,7 +127,7 @@ function Get-TargetResource $sqlCollation = $databaseServer.Collation - $sqlSystemAdminAccounts = @() + $sqlSystemAdminAccounts = @() foreach ($sqlUser in $databaseServer.Logins) { foreach ($sqlRole in $sqlUser.ListMembers()) @@ -129,13 +138,13 @@ function Get-TargetResource } } } - + if ($databaseServer.LoginMode -eq 'Mixed') { $securityMode = 'SQL' } else - { + { $securityMode = 'Windows' } @@ -143,25 +152,55 @@ function Get-TargetResource $sqlUserDatabaseDirectory = $databaseServer.DefaultFile $sqlUserDatabaseLogDirectory = $databaseServer.DefaultLog $sqlBackupDirectory = $databaseServer.BackupDirectory + + if ($databaseServer.IsClustered) + { + New-VerboseMessage -Message 'Clustered instance detected' + + $clusteredSqlInstance = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Resource -Filter "Type = 'SQL Server'" | + Where-Object { $_.PrivateProperties.InstanceName -eq $InstanceName } + + if (!$clusteredSqlInstance) + { + throw New-TerminatingError -ErrorType FailoverClusterResourceNotFound -FormatArgs $InstanceName -ErrorCategory 'ObjectNotFound' + } + + New-VerboseMessage -Message 'Clustered SQL Server resource located' + + $clusteredSqlGroup = $clusteredSqlInstance | Get-CimAssociatedInstance -ResultClassName MSCluster_ResourceGroup + $clusteredSqlNetworkName = $clusteredSqlGroup | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | + Where-Object { $_.Type -eq "Network Name" } + + $clusteredSqlIPAddress = ($clusteredSqlNetworkName | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | + Where-Object { $_.Type -eq "IP Address" }).PrivateProperties.Address + + # Extract the required values + $clusteredSqlGroupName = $clusteredSqlGroup.Name + $clusteredSqlHostname = $clusteredSqlNetworkName.PrivateProperties.DnsName + } + else + { + New-VerboseMessage -Message 'Clustered instance not detected' + } } if ($services | Where-Object {$_.Name -eq $fullTextServiceName}) { $features += 'FULLTEXT,' - $fulltextServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $fullTextServiceName}).StartName + $fulltextServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$fullTextServiceName'").StartName } if ($services | Where-Object {$_.Name -eq $reportServiceName}) { $features += 'RS,' - $reportingServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $reportServiceName}).StartName + $reportingServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$reportServiceName'").StartName } if ($services | Where-Object {$_.Name -eq $analysisServiceName}) { $features += 'AS,' - $analysisServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $analysisServiceName}).StartName - + $analysisServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$analysisServiceName'").StartName + $analysisServer = Connect-SQLAnalysis -SQLServer localhost -SQLInstanceName $InstanceName $analysisCollation = $analysisServer.ServerProperties['CollationName'].Value @@ -178,53 +217,55 @@ function Get-TargetResource if ($services | Where-Object {$_.Name -eq $integrationServiceName}) { $features += 'IS,' - $integrationServiceAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $integrationServiceName}).StartName + $integrationServiceAccountUsername = (Get-CimInstance -ClassName Win32_Service -Filter "Name = '$integrationServiceName'").StartName } - $products = Get-WmiObject -Class Win32_Product + $registryUninstallPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' - switch ($sqlVersion) - { - '10' - { - $identifyingNumber = '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' - } + # Verify if SQL Server Management Studio 2008 or SQL Server Management Studio 2008 R2 (major version 10) is installed + $installedProductSqlServerManagementStudio2008R2 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' + ) -ErrorAction SilentlyContinue - '11' - { - $identifyingNumber = '{A7037EB2-F953-4B12-B843-195F4D988DA1}' - } + # Verify if SQL Server Management Studio 2012 (major version 11) is installed + $installedProductSqlServerManagementStudio2012 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{A7037EB2-F953-4B12-B843-195F4D988DA1}' + ) -ErrorAction SilentlyContinue - '12' - { - $identifyingNumber = '{75A54138-3B98-4705-92E4-F619825B121F}' - } - } + # Verify if SQL Server Management Studio 2014 (major version 12) is installed + $installedProductSqlServerManagementStudio2014 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{75A54138-3B98-4705-92E4-F619825B121F}' + ) -ErrorAction SilentlyContinue - if ($products | Where-Object {$_.IdentifyingNumber -eq $identifyingNumber}) + if ( + ($sqlVersion -eq 10 -and $installedProductSqlServerManagementStudio2008R2) -or + ($sqlVersion -eq 11 -and $installedProductSqlServerManagementStudio2012) -or + ($sqlVersion -eq 12 -and $installedProductSqlServerManagementStudio2014) + ) { $features += 'SSMS,' } - switch ($sqlVersion) - { - '10' - { - $identifyingNumber = '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' - } - - '11' - { - $identifyingNumber = '{7842C220-6E9A-4D5A-AE70-0E138271F883}' - } - - '12' - { - $identifyingNumber = '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' - } - } - - if ($Products | Where-Object {$_.IdentifyingNumber -eq $identifyingNumber}) + # Evaluating if SQL Server Management Studio Advanced 2008 or SQL Server Management Studio Advanced 2008 R2 (major version 10) is installed + $installedProductSqlServerManagementStudioAdvanced2008R2 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' + ) -ErrorAction SilentlyContinue + + # Evaluating if SQL Server Management Studio Advanced 2012 (major version 11) is installed + $installedProductSqlServerManagementStudioAdvanced2012 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{7842C220-6E9A-4D5A-AE70-0E138271F883}' + ) -ErrorAction SilentlyContinue + + # Evaluating if SQL Server Management Studio Advanced 2014 (major version 12) is installed + $installedProductSqlServerManagementStudioAdvanced2014 = Get-ItemProperty -Path ( + Join-Path -Path $registryUninstallPath -ChildPath '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' + ) -ErrorAction SilentlyContinue + + if ( + ($sqlVersion -eq 10 -and $installedProductSqlServerManagementStudioAdvanced2008R2) -or + ($sqlVersion -eq 11 -and $installedProductSqlServerManagementStudioAdvanced2012) -or + ($sqlVersion -eq 12 -and $installedProductSqlServerManagementStudioAdvanced2014) + ) { $features += 'ADV_SSMS,' } @@ -274,7 +315,6 @@ function Get-TargetResource return @{ SourcePath = $SourcePath - SourceFolder = $SourceFolder Features = $features InstanceName = $InstanceName InstanceID = $instanceID @@ -303,6 +343,9 @@ function Get-TargetResource ASTempDir = $analysisTempDirectory ASConfigDir = $analysisConfigDirectory ISSvcAccountUsername = $integrationServiceAccountUsername + FailoverClusterGroupName = $clusteredSqlGroupName + FailoverClusterNetworkName = $clusteredSqlHostname + FailoverClusterIPAddress = $clusteredSqlIPAddress } } @@ -310,17 +353,24 @@ function Get-TargetResource .SYNOPSIS Installs the SQL Server features to the node. + .PARAMETER Action + The action to be performed. Default value is 'Install'. + Possible values are 'Install', 'InstallFailoverCluster', 'AddNode', 'PrepareFailoverCluster', and 'CompleteFailoverCluster' + .PARAMETER SourcePath - The path to the root of the source files for installation. I.e and UNC path to a shared resource. - - .PARAMETER SourceFolder - Folder within the source path containing the source files for installation. Default value is 'Source'. + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SetupCredential Credential to be used to perform the installation. .PARAMETER SourceCredential - Credential to be used to access SourcePath. + Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy + of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. + For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' + has in the path set, again, by the parameter `SourcePath`. + If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf + folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a + leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER SuppressReboot Suppressed reboot. @@ -337,7 +387,7 @@ function Get-TargetResource .PARAMETER InstanceID SQL instance ID, if different from InstanceName. - .PARAMETER PID + .PARAMETER ProductKey Product key for licensed installations. .PARAMETER UpdateEnabled @@ -432,17 +482,29 @@ function Get-TargetResource .PARAMETER BrowserSvcStartupType Specifies the startup mode for SQL Server Browser service + + .PARAMETER FailoverClusterGroupName + The name of the resource group to create for the clustered SQL Server instance + + .PARAMETER FailoverClusterIPAddress + Array of IP Addresses to be assigned to the clustered SQL Server instance + + .PARAMETER FailoverClusterNetworkName + Host name to be assigned to the clustered SQL Server instance #> function Set-TargetResource { + # Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot, either by force or when there are pending changes. + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] [CmdletBinding()] param ( + [ValidateSet('Install','InstallFailoverCluster','AddNode','PrepareFailoverCluster','CompleteFailoverCluster')] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", + $Action = 'Install', [System.String] - $SourceFolder = 'Source', + $SourcePath, [parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] @@ -468,7 +530,7 @@ function Set-TargetResource $InstanceID, [System.String] - $PID, + $ProductKey, [System.String] $UpdateEnabled, @@ -562,43 +624,63 @@ function Set-TargetResource [System.String] [ValidateSet('Automatic', 'Disabled', 'Manual')] - $BrowserSvcStartupType + $BrowserSvcStartupType, + + [System.String] + $FailoverClusterGroupName = "SQL Server ($InstanceName)", + + [System.String[]] + $FailoverClusterIPAddress, + + [System.String] + $FailoverClusterNetworkName ) $parameters = @{ SourcePath = $SourcePath - SourceFolder = $SourceFolder SetupCredential = $SetupCredential SourceCredential = $SourceCredential InstanceName = $InstanceName } - $sqlData = Get-TargetResource @parameters + $getTargetResourceResult = Get-TargetResource @parameters $InstanceName = $InstanceName.ToUpper() - $mediaSourcePath = (Join-Path -Path $SourcePath -ChildPath $SourceFolder) + $SourcePath = [Environment]::ExpandEnvironmentVariables($SourcePath) if ($SourceCredential) { - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Present' + $newSmbMappingParameters = @{ + RemotePath = $SourcePath + UserName = "$($SourceCredential.GetNetworkCredential().Domain)\$($SourceCredential.GetNetworkCredential().UserName)" + Password = $($SourceCredential.GetNetworkCredential().Password) + } - $tempPath = Get-TemporaryFolder - $mediaDestinationPath = (Join-Path -Path $tempPath -ChildPath $SourceFolder) + $null = New-SmbMapping @newSmbMappingParameters - New-VerboseMessage -Message "Robocopy is copying media from source '$mediaSourcePath' to destination '$mediaDestinationPath'" - Copy-ItemWithRoboCopy -Path $mediaSourcePath -DestinationPath $mediaDestinationPath - - NetUse -SourcePath $SourcePath -Credential $SourceCredential -Ensure 'Absent' + # 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 + + New-VerboseMessage -Message "Robocopy is copying media from source '$SourcePath' to destination '$mediaDestinationPath'" + Copy-ItemWithRoboCopy -Path $SourcePath -DestinationPath $mediaDestinationPath - $mediaSourcePath = $mediaDestinationPath + Remove-SmbMapping -RemotePath $SourcePath -Force + + $SourcePath = $mediaDestinationPath } - $path = ResolvePath (Join-Path -Path $mediaSourcePath -ChildPath 'setup.exe') - - New-VerboseMessage -Message "Using path: $path" - - $sqlVersion = GetSQLVersion -Path $path + $pathToSetupExecutable = Join-Path -Path $SourcePath -ChildPath 'setup.exe' + + New-VerboseMessage -Message "Using path: $pathToSetupExecutable" + + $sqlVersion = Get-SqlMajorVersion -Path $pathToSetupExecutable # Determine features to install $featuresToInstall = "" @@ -612,12 +694,12 @@ function Set-TargetResource Throw New-TerminatingError -ErrorType FeatureNotSupported -FormatArgs @($feature) -ErrorCategory InvalidData } - if (!($sqlData.Features.Contains($feature))) + if (!($getTargetResourceResult.Features.Contains($feature))) { $featuresToInstall += "$feature," } } - + $Features = $featuresToInstall.Trim(',') # If SQL shared components already installed, clear InstallShared*Dir variables @@ -693,15 +775,120 @@ function Set-TargetResource } } - # Create install arguments - $arguments = "/Quiet=`"True`" /IAcceptSQLServerLicenseTerms=`"True`" /Action=`"Install`"" + $setupArguments = @{} + + if ($Action -in @('PrepareFailoverCluster','CompleteFailoverCluster','InstallFailoverCluster','AddNode')) + { + # Set the group name for this clustered instance. + $setupArguments += @{ + FailoverClusterGroup = $FailoverClusterGroupName + + # This was brought over from the old module. Should be removed (breaking change). + SkipRules = 'Cluster_VerifyForErrors' + } + } + + # Perform disk mapping for specific cluster installation types + if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster')) + { + $failoverClusterDisks = @() + + # Get a required lising of drives based on user parameters + $requiredDrives = Get-Variable -Name 'SQL*Dir' -ValueOnly | Where-Object { -not [String]::IsNullOrEmpty($_) } | Split-Path -Qualifier | Sort-Object -Unique + + # Get the disk resources that are available (not assigned to a cluster role) + $availableStorage = Get-CimInstance -Namespace 'root/MSCluster' -ClassName 'MSCluster_ResourceGroup' -Filter "Name = 'Available Storage'" | + Get-CimAssociatedInstance -Association MSCluster_ResourceGroupToResource -ResultClassName MSCluster_Resource + + foreach ($diskResource in $availableStorage) + { + # Determine whether the current node is a possible owner of the disk resource + $possibleOwners = $diskResource | Get-CimAssociatedInstance -Association 'MSCluster_ResourceToPossibleOwner' -KeyOnly | Select-Object -ExpandProperty Name + + if ($possibleOwners -icontains $env:COMPUTERNAME) + { + # Determine whether this disk contains one of our required partitions + if ($requiredDrives -icontains ($diskResource | Get-CimAssociatedInstance -ResultClassName 'MSCluster_DiskPartition' | Select-Object -ExpandProperty Path)) + { + $failoverClusterDisks += $diskResource.Name + } + } + } + + # Ensure we have a unique listing of disks + $failoverClusterDisks = $failoverClusterDisks | Sort-Object -Unique + + # Ensure we mapped all required drives + $requiredDriveCount = $requiredDrives.Count + $mappedDriveCount = $failoverClusterDisks.Count + + if ($mappedDriveCount -ne $requiredDriveCount) + { + throw New-TerminatingError -ErrorType FailoverClusterDiskMappingError -FormatArgs ($failoverClusterDisks -join '; ') -ErrorCategory InvalidResult + } + + # Add the cluster disks as a setup argument + $setupArguments += @{ FailoverClusterDisks = ($failoverClusterDisks | Sort-Object) } + } + + # Determine network mapping for specific cluster installation types + if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster','AddNode')) + { + $clusterIPAddresses = @() + + # If no IP Address has been specified, use "DEFAULT" + if ($FailoverClusterIPAddress.Count -eq 0) + { + $clusterIPAddresses += "DEFAULT" + } + else + { + # Get the available client networks + $availableNetworks = @(Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Network -Filter 'Role >= 2') + + # Add supplied IP Addresses that are valid for available cluster networks + foreach ($address in $FailoverClusterIPAddress) + { + foreach ($network in $availableNetworks) + { + # Determine whether the IP address is valid for this network + if (Test-IPAddress -IPAddress $address -NetworkID $network.Address -SubnetMask $network.AddressMask) + { + # Add the formatted string to our array + $clusterIPAddresses += "IPv4; $address; $($network.Name); $($network.AddressMask)" + } + } + } + } + + # Ensure we mapped all required networks + $suppliedNetworkCount = $FailoverClusterIPAddress.Count + $mappedNetworkCount = $clusterIPAddresses.Count + + # Determine whether we have mapping issues for the IP Address(es) + if ($mappedNetworkCount -lt $suppliedNetworkCount) + { + throw New-TerminatingError -ErrorType FailoverClusterIPAddressNotValid -ErrorCategory InvalidArgument + } + + # Add the networks to the installation arguments + $setupArguments += @{ FailoverClusterIPAddresses = $clusterIPAddresses } + } + + # Add standard install arguments + $setupArguments += @{ + Quiet = $true + IAcceptSQLServerLicenseTerms = $true + Action = $Action + } + $argumentVars = @( 'InstanceName', 'InstanceID', 'UpdateEnabled', 'UpdateSource', 'Features', - 'PID', + 'ProductKey', 'SQMReporting', 'ErrorReporting', 'InstallSharedDir', @@ -709,7 +896,7 @@ function Set-TargetResource 'InstanceDir' ) - if ($BrowserSvcStartupType -ne $null) + if ($null -ne $BrowserSvcStartupType) { $argumentVars += 'BrowserSvcStartupType' } @@ -729,63 +916,41 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('SQLSvcAccount')) { - if ($SQLSvcAccount.UserName -eq "SYSTEM") - { - $arguments += " /SQLSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /SQLSVCACCOUNT=`"" + $SQLSvcAccount.UserName + "`"" - $arguments += " /SQLSVCPASSWORD=`"" + $SQLSvcAccount.GetNetworkCredential().Password + "`"" - } + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $SQLSvcAccount -ServiceType 'SQL') } if($PSBoundParameters.ContainsKey('AgtSvcAccount')) { - if($AgtSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /AGTSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /AGTSVCACCOUNT=`"" + $AgtSvcAccount.UserName + "`"" - $arguments += " /AGTSVCPASSWORD=`"" + $AgtSvcAccount.GetNetworkCredential().Password + "`"" - } + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $AgtSvcAccount -ServiceType 'AGT') + } + $setupArguments += @{ SQLSysAdminAccounts = @($SetupCredential.UserName) } + if ($PSBoundParameters -icontains 'SQLSysAdminAccounts') + { + $setupArguments['SQLSysAdminAccounts'] += $SQLSysAdminAccounts + } + + if ($SecurityMode -eq 'SQL') + { + $setupArguments += @{ SAPwd = $SAPwd.GetNetworkCredential().Password } } - $arguments += ' /AGTSVCSTARTUPTYPE=Automatic' + $setupArguments += @{ AgtSvcStartupType = 'Automatic' } } if ($Features.Contains('FULLTEXT')) { if ($PSBoundParameters.ContainsKey('FTSvcAccount')) { - if ($FTSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /FTSVCACCOUNT=`"NT AUTHORITY\LOCAL SERVICE`"" - } - else - { - $arguments += " /FTSVCACCOUNT=`"" + $FTSvcAccount.UserName + "`"" - $arguments += " /FTSVCPASSWORD=`"" + $FTSvcAccount.GetNetworkCredential().Password + "`"" - } + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $FTSvcAccount -ServiceType 'FT') } } if ($Features.Contains('RS')) { - if ($PSBoundParameters.ContainsKey("RSSvcAccount")) + if ($PSBoundParameters.ContainsKey('RSSvcAccount')) { - if ($RSSvcAccount.UserName -eq "SYSTEM") - { - $arguments += " /RSSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /RSSVCACCOUNT=`"" + $RSSvcAccount.UserName + "`"" - $arguments += " /RSSVCPASSWORD=`"" + $RSSvcAccount.GetNetworkCredential().Password + "`"" - } + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $RSSvcAccount -ServiceType 'RS') } } @@ -802,69 +967,71 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey('ASSvcAccount')) { - if($ASSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /ASSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /ASSVCACCOUNT=`"" + $ASSvcAccount.UserName + "`"" - $arguments += " /ASSVCPASSWORD=`"" + $ASSvcAccount.GetNetworkCredential().Password + "`"" - } + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $ASSvcAccount -ServiceType 'AS') } - } - if ($Features.Contains('IS')) - { - if ($PSBoundParameters.ContainsKey('ISSvcAccount')) + $setupArguments += @{ ASSysAdminAccounts = @($SetupCredential.UserName) } + + if($PSBoundParameters.ContainsKey("ASSysAdminAccounts")) { - if ($ISSvcAccount.UserName -eq 'SYSTEM') - { - $arguments += " /ISSVCACCOUNT=`"NT AUTHORITY\SYSTEM`"" - } - else - { - $arguments += " /ISSVCACCOUNT=`"" + $ISSvcAccount.UserName + "`"" - $arguments += " /ISSVCPASSWORD=`"" + $ISSvcAccount.GetNetworkCredential().Password + "`"" - } + $setupArguments['ASSysAdminAccounts'] += $ASSysAdminAccounts } } - foreach ($argumentVar in $argumentVars) + if ($Features.Contains('IS')) { - if ((Get-Variable -Name $argumentVar).Value -ne '') + if ($PSBoundParameters.ContainsKey('ISSvcAccount')) { - $arguments += " /$argumentVar=`"" + (Get-Variable -Name $argumentVar).Value + "`"" + $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $ISSvcAccount -ServiceType 'IS') } } - if ($Features.Contains('SQLENGINE')) + # Automatically include any additional arguments + foreach ($argument in $argumentVars) { - $arguments += " /SQLSysAdminAccounts=`"" + $SetupCredential.UserName + "`"" - if ($PSBoundParameters.ContainsKey('SQLSysAdminAccounts')) + if($argument -eq 'ProductKey') { - foreach ($adminAccount in $SQLSysAdminAccounts) - { - $arguments += " `"$adminAccount`"" - } + $setupArguments += @{ 'PID' = (Get-Variable -Name $argument -ValueOnly) } } - - if ($SecurityMode -eq 'SQL') + else { - $arguments += " /SAPwd=" + $SAPwd.GetNetworkCredential().Password + $setupArguments += @{ $argument = (Get-Variable -Name $argument -ValueOnly) } } } - if ($Features.Contains('AS')) + # Build the argument string to be passed to setup + $arguments = '' + foreach ($currentSetupArgument in $setupArguments.GetEnumerator()) { - $arguments += " /ASSysAdminAccounts=`"" + $SetupCredential.UserName + "`"" - if($PSBoundParameters.ContainsKey("ASSysAdminAccounts")) + if ($currentSetupArgument.Value -ne '') { - foreach($adminAccount in $ASSysAdminAccounts) + # Arrays are handled specially + if ($currentSetupArgument.Value -is [array]) + { + # Sort and format the array + $setupArgumentValue = ($currentSetupArgument.Value | Sort-Object | ForEach-Object { '"{0}"' -f $_ }) -join ' ' + } + elseif ($currentSetupArgument.Value -is [Boolean]) + { + $setupArgumentValue = @{ $true = 'True'; $false = 'False' }[$currentSetupArgument.Value] + $setupArgumentValue = '"{0}"' -f $setupArgumentValue + } + else { - $arguments += " `"$adminAccount`"" + # Features are comma-separated, no quotes + if ($currentSetupArgument.Key -eq 'Features') + { + $setupArgumentValue = $currentSetupArgument.Value + } + else + { + $setupArgumentValue = '"{0}"' -f $currentSetupArgument.Value + } } + + $arguments += "/$($currentSetupArgument.Key.ToUpper())=$($setupArgumentValue) " } + } # Replace sensitive values for verbose output @@ -874,9 +1041,9 @@ function Set-TargetResource $log = $log.Replace($SAPwd.GetNetworkCredential().Password,"********") } - if ($PID -ne "") + if ($ProductKey -ne "") { - $log = $log.Replace($PID,"*****-*****-*****-*****-*****") + $log = $log.Replace($ProductKey,"*****-*****-*****-*****-*****") } $logVars = @('AgtSvcAccount', 'SQLSvcAccount', 'FTSvcAccount', 'RSSvcAccount', 'ASSvcAccount','ISSvcAccount') @@ -890,9 +1057,11 @@ function Set-TargetResource New-VerboseMessage -Message "Starting setup using arguments: $log" - $process = StartWin32Process -Path $path -Arguments $arguments + $arguments = $arguments.Trim() + $process = StartWin32Process -Path $pathToSetupExecutable -Arguments $arguments + New-VerboseMessage -Message $process - WaitForWin32ProcessEnd -Path $path -Arguments $arguments + WaitForWin32ProcessEnd -Path $pathToSetupExecutable -Arguments $arguments if ($ForceReboot -or ($null -ne (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue))) { @@ -916,17 +1085,24 @@ function Set-TargetResource .SYNOPSIS Tests if the SQL Server features are installed on the node. + .PARAMETER Action + The action to be performed. Default value is 'Install'. + Possible values are 'Install', 'InstallFailoverCluster', 'AddNode', 'PrepareFailoverCluster', and 'CompleteFailoverCluster' + .PARAMETER SourcePath - The path to the root of the source files for installation. I.e and UNC path to a shared resource. - - .PARAMETER SourceFolder - Folder within the source path containing the source files for installation. Default value is 'Source'. + The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. .PARAMETER SetupCredential Credential to be used to perform the installation. .PARAMETER SourceCredential - Credential to be used to access SourcePath. + Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy + of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. + For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' + has in the path set, again, by the parameter `SourcePath`. + If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf + folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a + leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. .PARAMETER SuppressReboot Suppresses reboot. @@ -943,7 +1119,7 @@ function Set-TargetResource .PARAMETER InstanceID SQL instance ID, if different from InstanceName. - .PARAMETER PID + .PARAMETER ProductKey Product key for licensed installations. .PARAMETER UpdateEnabled @@ -1038,6 +1214,15 @@ function Set-TargetResource .PARAMETER BrowserSvcStartupType Specifies the startup mode for SQL Server Browser service + + .PARAMETER FailoverClusterGroupName + The name of the resource group to create for the clustered SQL Server instance + + .PARAMETER FailoverClusterIPAddress + Array of IP Addresses to be assigned to the clustered SQL Server instance + + .PARAMETER FailoverClusterNetworkName + Host name to be assigned to the clustered SQL Server instance #> function Test-TargetResource { @@ -1045,11 +1230,12 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( + [ValidateSet('Install','InstallFailoverCluster','AddNode','PrepareFailoverCluster','CompleteFailoverCluster')] [System.String] - $SourcePath = "$PSScriptRoot\..\..\", + $Action = 'Install', [System.String] - $SourceFolder = 'Source', + $SourcePath, [parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] @@ -1075,7 +1261,7 @@ function Test-TargetResource $InstanceID, [System.String] - $PID, + $ProductKey, [System.String] $UpdateEnabled, @@ -1169,23 +1355,34 @@ function Test-TargetResource [System.String] [ValidateSet('Automatic', 'Disabled', 'Manual')] - $BrowserSvcStartupType + $BrowserSvcStartupType, + + [Parameter(ParameterSetName = 'ClusterInstall')] + [System.String] + $FailoverClusterGroupName = "SQL Server ($InstanceName)", + + [Parameter(ParameterSetName = 'ClusterInstall')] + [System.String[]] + $FailoverClusterIPAddress, + + [Parameter(ParameterSetName = 'ClusterInstall')] + [System.String] + $FailoverClusterNetworkName ) $parameters = @{ SourcePath = $SourcePath - SourceFolder = $SourceFolder SetupCredential = $SetupCredential SourceCredential = $SourceCredential InstanceName = $InstanceName } - $sqlData = Get-TargetResource @parameters - New-VerboseMessage -Message "Features found: '$($SQLData.Features)'" + $getTargetResourceResult = Get-TargetResource @parameters + New-VerboseMessage -Message "Features found: '$($getTargetResourceResult.Features)'" $result = $false - if ($sqlData.Features ) - { + if ($getTargetResourceResult.Features ) + { $result = $true foreach ($feature in $Features.Split(",")) @@ -1193,9 +1390,25 @@ function Test-TargetResource # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase $feature = $feature.ToUpper(); - if(!($sqlData.Features.Contains($feature))) + if(!($getTargetResourceResult.Features.Contains($feature))) { - New-VerboseMessage -Message "Unable to find feature '$feature' among the installed features: '$($sqlData.Features)'" + New-VerboseMessage -Message "Unable to find feature '$feature' among the installed features: '$($getTargetResourceResult.Features)'" + $result = $false + } + } + } + + if ($PSCmdlet.ParameterSetName -eq 'ClusterInstall') + { + New-VerboseMessage -Message "Clustered install, checking parameters." + + $result = $true + + Get-Variable -Name FailoverCluster* | ForEach-Object { + $variableName = $_.Name + + if ($getTargetResourceResult.$variableName -ne $_.Value) { + New-VerboseMessage -Message "$variableName '$($_.Value)' is not in the desired state for this cluster." $result = $false } } @@ -1211,7 +1424,7 @@ function Test-TargetResource .PARAMETER Path String containing the path to the SQL Server setup.exe executable. #> -function GetSQLVersion +function Get-SqlMajorVersion { [CmdletBinding()] param @@ -1226,10 +1439,10 @@ function GetSQLVersion <# .SYNOPSIS - Returns the first item value in the registry location provided in the Path parameter. + Returns the first item value in the registry location provided in the Path parameter. .PARAMETER Path - String containing the path to the registry. + String containing the path to the registry. #> function Get-FirstItemPropertyValue { @@ -1241,7 +1454,7 @@ function Get-FirstItemPropertyValue $Path ) - $registryProperty = Get-Item -Path $Path -ErrorAction SilentlyContinue + $registryProperty = Get-Item -Path $Path -ErrorAction SilentlyContinue if ($registryProperty) { $registryProperty = $registryProperty | Select-Object -ExpandProperty Property | Select-Object -First 1 @@ -1249,8 +1462,8 @@ function Get-FirstItemPropertyValue { $registryPropertyValue = (Get-ItemProperty -Path $Path -Name $registryProperty).$registryProperty.TrimEnd('\') } - } - + } + return $registryPropertyValue } @@ -1260,7 +1473,7 @@ function Get-FirstItemPropertyValue .PARAMETER Path Source path to be copied. - + .PARAMETER DestinationPath The path to the destination. #> @@ -1269,28 +1482,223 @@ function Copy-ItemWithRoboCopy [CmdletBinding()] param ( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $Path, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $DestinationPath ) - & robocopy.exe $Path $DestinationPath /e + $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 "Robocopy is using unbuffered I/O." + $robocopyArgumentUseUnbufferedIO = '/J' + } + else + { + Write-Verbose 'Unbuffered I/O cannot be used due to incompatible version of Robocopy.' + } + + $robocopyArgumentList = '{0} {1} {2} {3} {4} {5}' -f $Path, + $DestinationPath, + $robocopyArgumentCopySubDirectoriesIncludingEmpty, + $robocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource, + $robocopyArgumentUseUnbufferedIO, + $robocopyArgumentSilent + + $robocopyStartProcessParameters = @{ + FilePath = $robocopyExecutable.Name + ArgumentList = $robocopyArgumentList + } + + Write-Verbose ('Robocopy is started with the following arguments: {0}' -f $robocopyArgumentList ) + $robocopyProcess = Start-Process @robocopyStartProcessParameters -Wait -NoNewWindow -PassThru + + switch ($($robocopyProcess.ExitCode)) + { + {$_ -in 8, 16} + { + throw "Robocopy reported errors when copying files. Error code: $_." + } + + {$_ -gt 7 } + { + throw "Robocopy reported that failures occured when copying files. Error code: $_." + } + + 1 + { + Write-Verbose 'Robocopy copied files sucessfully' + } + + 2 + { + Write-Verbose 'Robocopy found files at the destination path that is not present at the source path, these extra files was remove at the destination path.' + } + + 3 + { + Write-Verbose 'Robocopy copied files to destination sucessfully. 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.' + } + + {$_ -eq 0 -or $null -eq $_ } + { + Write-Verbose 'Robocopy reported that all files already present.' + } + } } <# .SYNOPSIS - Returns the path of the current user's temporary folder. + 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 + + .PARAMETER IPAddress + The IP Address to be converted +#> +function ConvertTo-Decimal +{ + [CmdletBinding()] + [OutputType([System.UInt32])] + param( + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $IPAddress + ) + + $i = 3 + $DecimalIP = 0 + $IPAddress.GetAddressBytes() | ForEach-Object { + $DecimalIP += $_ * [Math]::Pow(256,$i) + $i-- + } + + return [UInt32]$DecimalIP +} + +<# + .SYNOPSIS + Determines whether an IP Address is valid for a given network / subnet + + .PARAMETER IPAddress + IP Address to be checked + + .PARAMETER NetworkID + IP Address of the network identifier + + .PARAMETER SubnetMask + Subnet mask of the network to be checked +#> +function Test-IPAddress +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $IPAddress, + + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $NetworkID, + + [Parameter(Mandatory = $true)] + [System.Net.IPAddress] + $SubnetMask + ) + + # Convert all values to decimal + $IPAddressDecimal = ConvertTo-Decimal -IPAddress $IPAddress + $NetworkDecimal = ConvertTo-Decimal -IPAddress $NetworkID + $SubnetDecimal = ConvertTo-Decimal -IPAddress $SubnetMask + + # Determine whether the IP Address is valid for this network / subnet + return (($IPAddressDecimal -band $SubnetDecimal) -eq ($NetworkDecimal -band $SubnetDecimal)) +} + +<# + .SYNOPSIS + Builds service account parameters for setup + + .PARAMETER ServiceAccount + Credential for the service account + + .PARAMETER ServiceType + Type of service account +#> +function Get-ServiceAccountParameters +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [PSCredential] + $ServiceAccount, + + [Parameter(Mandatory = $true)] + [ValidateSet('SQL','AGT','IS','RS','AS','FT')] + [String] + $ServiceType + ) + + $parameters = @{} + + switch -Regex ($ServiceAccount.UserName.ToUpper()) + { + '^(?:NT ?AUTHORITY\\)?(SYSTEM|LOCALSERVICE|LOCAL SERVICE|NETWORKSERVICE|NETWORK SERVICE)$' + { + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = "NT AUTHORITY\$($Matches[1])" + } + } + + '^(?:NT SERVICE\\)(.*)$' + { + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = "NT SERVICE\$($Matches[1])" + } + } + + '.*\$' + { + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = $ServiceAccount.UserName + } + } + + default + { + $parameters = @{ + "$($ServiceType)SVCACCOUNT" = $ServiceAccount.UserName + "$($ServiceType)SVCPASSWORD" = $ServiceAccount.GetNetworkCredential().Password + } + } + } + + return $parameters +} + Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof index 36275b7c6..fa3e30b52 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.schema.mof @@ -1,16 +1,16 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerSetup")] class MSFT_xSQLServerSetup : OMI_BaseResource { - [Write, Description("The path to the root of the source files for installation. I.e and UNC path to a shared resource.")] String SourcePath; - [Write, Description("Folder within the source path containing the source files for installation. Default value is 'Source'.")] String SourceFolder; + [Write, Description("The action to be performed. Default value is 'Install'."), ValueMap{"Install","InstallFailoverCluster","AddNode","PrepareFailoverCluster","CompleteFailoverCluster"}, Values{"Install","InstallFailoverCluster","AddNode","PrepareFailoverCluster","CompleteFailoverCluster"}] String Action; + [Write, Description("The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path.")] String SourcePath; [Required, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to perform the installation.")] String SetupCredential; - [Write, EmbeddedInstance("MSFT_Credential"), Description("Credential to be used to access SourcePath.")] String SourceCredential; + [Write, EmbeddedInstance("MSFT_Credential"), Description("Credentials used to access the path set in the parameter 'SourcePath'.")] String SourceCredential; [Write, Description("Suppresses reboot.")] Boolean SuppressReboot; [Write, Description("Forces reboot.")] Boolean ForceReboot; [Write, Description("SQL features to be installed.")] String Features; [Key, Description("Name of the SQL instance to be installed.")] String InstanceName; [Write, Description("SQL instance ID, if different from InstanceName.")] String InstanceID; - [Write, Description("Product key for licensed installations.")] String PID; + [Write, Description("Product key for licensed installations.")] String ProductKey; [Write, Description("Enabled updates during installation.")] String UpdateEnabled; [Write, Description("Path to the source of updates to be applied during installation.")] String UpdateSource; [Write, Description("Enable customer experience reporting.")] String SQMReporting; @@ -48,4 +48,7 @@ class MSFT_xSQLServerSetup : OMI_BaseResource [Write, EmbeddedInstance("MSFT_Credential"), Description("Service account for Integration Services service.")] String ISSvcAccount; [Read, Description("Output username for the Integration Services service.")] String ISSvcAccountUsername; [Write, Description("Specifies the startup mode for SQL Server Browser service."), ValueMap{"Automatic", "Disabled", "Manual"}, Values{"Automatic", "Disabled", "Manual"}] String BrowserSvcStartupType; + [Write, Description("The name of the resource group to create for the clustered SQL Server instance.")] String FailoverClusterGroupName; + [Write, Description("Array of IP Addresses to be assigned to the clustered SQL Server instance.")] String FailoverClusterIPAddress[]; + [Write, Description("Host name to be assigend to the clustered SQL Server instance.")] String FailoverClusterNetworkName; }; diff --git a/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 b/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 index 8257da583..3f42a7645 100644 --- a/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 +++ b/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1 @@ -1,17 +1,17 @@ <# .EXAMPLE This example shows how to ensure that the SQL Alias - SQLDSC* exists with Named Pipes or TCP. + SQLDSC* exists with Named Pipes or TCP. #> -Configuration Example +Configuration Example { param( [Parameter(Mandatory = $true)] [PSCredential] $SysAdminAccount ) - + Import-DscResource -ModuleName xSqlServer node localhost { diff --git a/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 b/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 index 85fd1c2ab..189725b2f 100644 --- a/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 +++ b/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1 @@ -1,42 +1,39 @@ <# .EXAMPLE This example shows how to ensure that the user account CONTOSO\SQLAdmin - is "Owner" of SQL database "AdventureWorks". + is "Owner" of SQL database "AdventureWorks". #> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) - Configuration Example - { - param - ( - [Parameter(Mandatory = $true)] - [System.Management.Automation.PSCredential] - [System.Management.Automation.Credential()] - $SysAdminAccount - ) - - Import-DscResource -ModuleName xSqlServer + Import-DscResource -ModuleName xSqlServer - node localhost + node localhost + { + xSQLServerLogin Add_SqlServerLogin_SQLAdmin { - xSQLServerLogin Add_SqlServerLogin_SQLAdmin - { - DependsOn = '[xSqlServerSetup]SETUP_SqlMSSQLSERVER' - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - LoginType = 'WindowsUser' - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } - xSQLServerDatabaseOwner Set_SqlDatabaseOwner_SQLAdmin - { - DependsOn = '[xSQLServerLogin]Add_SqlServerLogin_SQLAdmin' - Name = 'CONTOSO\SQLAdmin' - Database = 'AdventureWorks' - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + xSQLServerDatabaseOwner Set_SqlDatabaseOwner_SQLAdmin + { + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } +} diff --git a/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1 b/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1 new file mode 100644 index 000000000..ad468721b --- /dev/null +++ b/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1 @@ -0,0 +1,74 @@ +<# +.DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + has "Connect" and "Update" SQL Permissions for database "AdventureWorks". +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_SqlServerLogin_SQLAdmin + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_SqlServerLogin_SQLUser + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Grant_SqlDatabasePermissions_SQLAdmin_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Grant_SqlDatabasePermissions_SQLUser_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Grant_SqlDatabasePermissions_SQLAdmin_Db02 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorksLT' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1 b/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1 new file mode 100644 index 000000000..e156e9f29 --- /dev/null +++ b/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1 @@ -0,0 +1,42 @@ +<# +.DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + hasn't "Select" and "Create Table" SQL Permissions for database "AdventureWorks". +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerDatabasePermission RevokeGrant_SqlDatabasePermissions_SQLAdmin + { + Ensure = 'Absent' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = 'Connect','Update' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission RevokeDeny_SqlDatabasePermissions_SQLAdmin + { + Ensure = 'Absent' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1 b/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1 new file mode 100644 index 000000000..32e589e1c --- /dev/null +++ b/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1 @@ -0,0 +1,74 @@ +<# +.DESCRIPTION + This example shows how to ensure that the user account CONTOSO\SQLAdmin + has "Connect" and "Update" SQL Permissions for database "AdventureWorks". +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_SqlServerLogin_SQLAdmin + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_SqlServerLogin_SQLUser + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Deny_SqlDatabasePermissions_SQLAdmin_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorks' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Deny_SqlDatabasePermissions_SQLUser_Db01 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLUser' + Database = 'AdventureWorks' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabasePermission Deny_SqlDatabasePermissions_SQLAdmin_Db02 + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + Database = 'AdventureWorksLT' + PermissionState = 'Deny' + Permissions = 'Select','Create Table' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1 b/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1 new file mode 100644 index 000000000..518047e16 --- /dev/null +++ b/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1 @@ -0,0 +1,57 @@ +<# +.EXAMPLE + This example shows how to set the Recovery Model + to "Full" for SQL database "AdventureWorks". +#> + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerDatabase Add_SqlDatabaseAdventureworks + { + Ensure = 'Present' + Name = 'Adventureworks' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabase Add_SqlDatabaseAdventureWorks2012 + { + Ensure = 'Present' + Name = 'AdventureWorks2012' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabaseRecoveryModel Set_SqlDatabaseRecoveryModel_Adventureworks + { + Name = 'Adventureworks' + RecoveryModel = 'Full' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerDatabaseRecoveryModel Set_SqlDatabaseRecoveryModel_AdventureWorks2012 + { + Name = 'AdventureWorks2012' + RecoveryModel = 'Simple' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1 b/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1 new file mode 100644 index 000000000..8834baee3 --- /dev/null +++ b/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1 @@ -0,0 +1,39 @@ +<# +.EXAMPLE + This example shows how to create the default rules for the supported features. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerFirewall Create_FirewallRules_For_SQL2012 + { + Ensure = 'Present' + Features = 'SQLENGINE,AS,RS,IS' + InstanceName = 'SQL2012' + SourcePath = '\\files.company.local\images\SQL2012' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerFirewall Create_FirewallRules_For_SQL2016 + { + Ensure = 'Present' + Features = 'SQLENGINE' + InstanceName = 'SQL2016' + SourcePath = '\\files.company.local\images\SQL2016' + + SourceCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1 b/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1 new file mode 100644 index 000000000..b06f60d36 --- /dev/null +++ b/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1 @@ -0,0 +1,39 @@ +<# +.EXAMPLE + This example shows how to remove the default rules for the supported features. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerFirewall Remove_FirewallRules_For_SQL2012 + { + Ensure = 'Absent' + Features = 'SQLENGINE,AS,RS,IS' + InstanceName = 'SQL2012' + SourcePath = '\\files.company.local\images\SQL2012' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerFirewall Remove_FirewallRules_For_SQL2016 + { + Ensure = 'Absent' + Features = 'SQLENGINE' + InstanceName = 'SQL2016' + SourcePath = '\\files.company.local\images\SQL2016' + + SourceCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 b/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 new file mode 100644 index 000000000..b844a81fd --- /dev/null +++ b/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 @@ -0,0 +1,61 @@ +<# +.EXAMPLE +This example shows how to ensure that the Windows user 'CONTOSO\WindowsUser' exists. + +.EXAMPLE +This example shows how to ensure that the Windows group 'CONTOSO\WindowsGroup' exists. + +.EXAMPLE +This example shows how to ensure that the SQL Login 'SqlLogin' exists. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount, + + [Parameter(Mandatory = $true)] + [PSCredential] + $LoginCredential + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_WindowsUser + { + Ensure = 'Present' + Name = 'CONTOSO\WindowsUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_WindowsGroup + { + Ensure = 'Present' + Name = 'CONTOSO\WindowsGroup' + LoginType = 'WindowsGroup' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerLogin Add_SqlLogin + { + Ensure = 'Present' + Name = 'SqlLogin' + LoginType = 'SqlLogin' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + LoginCredential = $LoginCredential + LoginMustChangePassword = $false + LoginPasswordExpirationEnabled = $true + LoginPasswordPolicyEnforced = $true + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerLogin/2-RemoveLogin.ps1 b/Examples/Resources/xSQLServerLogin/2-RemoveLogin.ps1 new file mode 100644 index 000000000..693ea39a8 --- /dev/null +++ b/Examples/Resources/xSQLServerLogin/2-RemoveLogin.ps1 @@ -0,0 +1,44 @@ +<# +.EXAMPLE +This example shows how to remove the Windows user 'CONTOSO\WindowsUser'. + +.EXAMPLE +This example shows how to remove Windows group 'CONTOSO\WindowsGroup'. + +.EXAMPLE +This example shows how to remove the SQL Login 'SqlLogin'. +#> + +Configuration Example +{ + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Remove_WindowsUser + { + Ensure = 'Absent' + Name = 'CONTOSO\WindowsUser' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + } + + xSQLServerLogin Remove_WindowsGroup + { + Ensure = 'Absent' + Name = 'CONTOSO\WindowsGroup' + LoginType = 'WindowsGroup' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + } + + xSQLServerLogin Remove_SqlLogin + { + Ensure = 'Absent' + Name = 'SqlLogin' + LoginType = 'SqlLogin' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + } + } +} diff --git a/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 b/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 index ee474fb55..6ee078eb1 100644 --- a/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 +++ b/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 @@ -1,29 +1,39 @@ <# .EXAMPLE This example shows how to ensure that the user account CONTOSO\SQLAdmin - has "dbcreator" and "securityadmin" SQL server roles. + has "dbcreator" and "securityadmin" SQL server roles. #> - Configuration Example - { - param( - [Parameter(Mandatory = $true)] - [PSCredential] - $SysAdminAccount - ) - - Import-DscResource -ModuleName xSqlServer +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) - node localhost { - xSQLServerRole Add_SqlServerRole_SQLAdmin - { - DependsOn = '[xSQLServerLogin]Add_SqlServerLogin_SQLAdmin' - Ensure = 'Present' - Name = 'CONTOSO\SQLAdmin' - ServerRole = "dbcreator","securityadmin" - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerLogin Add_LoginForSQLAdmin + { + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerRole Add_ServerRoleToLogin + { + DependsOn = '[xSQLServerLogin]Add_LoginForSQLAdmin' + Ensure = 'Present' + Name = 'CONTOSO\SQLAdmin' + ServerRole = "dbcreator","securityadmin" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } +} diff --git a/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 b/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 index bf9823a61..c39e89c6b 100644 --- a/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 +++ b/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 @@ -1,29 +1,28 @@ <# .EXAMPLE This example shows how to ensure that the user account CONTOSO\SQLUser - does not have "setupadmin" SQL server role. + does not have "setupadmin" SQL server role. #> - Configuration Example - { - param( - [Parameter(Mandatory = $true)] - [PSCredential] - $SysAdminAccount - ) - - Import-DscResource -ModuleName xSqlServer +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) - node localhost { - xSQLServerRole Add_SqlServerRole_SQLAdmin - { - DependsOn = '[xSQLServerLogin]Add_SqlServerLogin_SQLAdmin' - Ensure = 'Absent' - Name = 'CONTOSO\SQLUser' - ServerRole = "setupadmin" - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount - } + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerRole Remove_ServerRoleFromLogin + { + Ensure = 'Absent' + Name = 'CONTOSO\SQLUser' + ServerRole = "setupadmin" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } +} diff --git a/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 b/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 new file mode 100644 index 000000000..11d572f74 --- /dev/null +++ b/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1 @@ -0,0 +1,29 @@ +<# +.EXAMPLE + This example shows how to run SQL script using SQL Authentication. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SqlCredential + ) + + Import-DscResource -ModuleName xSQLServer + + Node localhost + { + xSQLServerScript 'RunSQLScript' + { + ServerInstance = 'localhost\SQL2016' + Credential = $SqlCredential + + SetFilePath = 'C:\DSCTemp\SQLScripts\Set-RunSQLScript.sql' + TestFilePath = 'C:\DSCTemp\SQLScripts\Test-RunSQLScript.sql' + GetFilePath = 'C:\DSCTemp\SQLScripts\Get-RunSQLScript.sql' + Variable = @("FilePath=C:\temp\log\AuditFiles") + } + } +} diff --git a/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 b/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 new file mode 100644 index 000000000..098e8fa77 --- /dev/null +++ b/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1 @@ -0,0 +1,41 @@ +<# +.EXAMPLE + These two example shows how to run SQL script using Windows Authentication. + First example shows how the resource is run as account SYSTEM. And the second example shows how the resource is run with a user account. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $WindowsCredential + ) + + Import-DscResource -ModuleName xSQLServer + + Node localhost + { + xSQLServerScript 'RunSQLScript-AsSYSTEM' + { + ServerInstance = 'localhost\SQL2016' + + SetFilePath = 'C:\DSCTemp\SQLScripts\Set-RunSQLScript-AsSYSTEM.sql' + TestFilePath = 'C:\DSCTemp\SQLScripts\Test-RunSQLScript-AsSYSTEM.sql' + GetFilePath = 'C:\DSCTemp\SQLScripts\Get-RunSQLScript-AsSYSTEM.sql' + Variable = @("FilePath=C:\temp\log\AuditFiles") + } + + xSQLServerScript 'RunSQLScript-AsUSER' + { + ServerInstance = 'localhost\SQL2016' + + SetFilePath = 'C:\DSCTemp\SQLScripts\Set-RunSQLScript-AsUSER.sql' + TestFilePath = 'C:\DSCTemp\SQLScripts\Test-RunSQLScript-AsUSER.sql' + GetFilePath = 'C:\DSCTemp\SQLScripts\Get-RunSQLScript-AsUSER.sql' + Variable = @("FilePath=C:\temp\log\AuditFiles") + + PsDscRunAsCredential = $WindowsCredential + } + } +} diff --git a/Examples/SQLPush_SingleServer.ps1 b/Examples/SQLPush_SingleServer.ps1 index 01b783c2b..8596ede98 100644 --- a/Examples/SQLPush_SingleServer.ps1 +++ b/Examples/SQLPush_SingleServer.ps1 @@ -128,7 +128,7 @@ Configuration SQLSA Database = "TestDB" Name = "TestUser2" } - xSQLServerDatabasePermissions($Node.Nodename) + xSQLServerDatabasePermission($Node.Nodename) { Database = "Model" Name = "TestUser1" diff --git a/Examples/SQLServerScript.ps1 b/Examples/SQLServerScript.ps1 deleted file mode 100644 index 922470347..000000000 --- a/Examples/SQLServerScript.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -configuration SQLSettings -{ - Import-DscResource -ModuleName 'xSQLServer' - - Node 'localhost' - { - xSQLServerScript SqlSettings - { - ServerInstance = "$env:COMPUTERNAME\SMA" - SetFilePath = "C:\temp\Set-SQlsettings.sql" - TestFilePath = "C:\temp\Test-SQlsettings.sql" - GetFilePath = "C:\temp\Get-SQlsettings.sql" - Variable = @("FilePath=C:\temp\log\AuditFiles") - } - } -} - -$configData = @{ - AllNodes = @( - @{ - NodeName = 'localhost' - } - ) -} - -SQLSettings -ConfigurationData $configData - -Start-DscConfiguration -Path .\SQLSettings -Wait -Force -Verbose diff --git a/README.md b/README.md index fdf8cee54..0396a2006 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,34 @@ [![Build status](https://ci.appveyor.com/api/projects/status/mxn453y284eab8li/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xsqlserver/branch/master) -The **xSQLServer** module contains DSC resources for deployment and configuration of SQL Server in a way that is fully compliant with the requirements of System Center. +The **xSQLServer** module contains DSC resources for deployment and configuration of SQL Server. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## Contributing -Please check out common DSC Resources [contributing guidelines](https://github.com/PowerShell/DscResource.Kit/blob/master/CONTRIBUTING.md). -Also please check out the [specific guidelines](https://github.com/PowerShell/xSQLServer/blob/dev/CONTRIBUTING.md) for contributing to xSQLServer. +Regardless of the way you want to contribute we are tremendously happy to have you here. + +There are several ways you can contribute. You can submit an issue to report a bug. You can submit an issue to request an improvment. You can take part in discussions for issues. You can review pull requests and comment on other contributors changes. +You can also improve the resources and tests, or even create new resources, by sending in pull requests yourself. + +* If you want to submit an issue or take part in discussions, please browse the list of [issues](https://github.com/PowerShell/xSQLServer/issues). Please check out [Contributing to the DSC Resource Kit](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) on how to work with issues. +* If you want to review pull requests, please first check out the [Review Pull Request guidelines](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md#reviewing-pull-requests), and the browse the list of [pull requests](https://github.com/PowerShell/xSQLServer/pulls) and look for those pull requests with label 'needs review'. +* If you want to improve the resources or tests, or create a new resource, then please check out the following guidelines. + * The [Contributing to the DSC Resource Kit](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md) guidelines. + * The specific [Contributing to xSQLServer](https://github.com/PowerShell/xSQLServer/blob/dev/CONTRIBUTING.md) guidelines. + * The common [Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md). + * The common [Best Practices](https://github.com/PowerShell/DscResources/blob/master/BestPractices.md) guidelines. + * The common [Testing Guidelines](https://github.com/PowerShell/DscResources/blob/master/TestsGuidelines.md). + * If you are new to GitHub (and git), then please check out [Getting Started with GitHub](https://github.com/PowerShell/DscResources/blob/master/GettingStartedWithGitHub.md). + * If you are new to Pester and writing test, then please check out [Getting started with Pester](https://github.com/PowerShell/DscResources/blob/master/GettingStartedWithPester.md). + +If you need any help along the way, don't be afraid to ask. We are here for each other. ## Installation -To manually install the module, download the source code and unzip the contents of the '\Modules\xSQLServer' directory to the '$env:ProgramFiles\WindowsPowerShell\Modules folder'. +To manually install the module, download the source code and unzip the contents of the '\Modules\xSQLServer' directory to the '$env:ProgramFiles\WindowsPowerShell\Modules' folder. To install from the PowerShell gallery using PowerShellGet (in PowerShell 5.0) run the following command: @@ -22,7 +37,7 @@ To install from the PowerShell gallery using PowerShellGet (in PowerShell 5.0) r Find-Module -Name xSQLServer -Repository PSGallery | Install-Module ``` -To confirm installation, run the below command and ensure you see the Office Online Server DSC resoures available: +To confirm installation, run the below command and ensure you see the SQL Server DSC resources available: ```powershell Get-DscResource -Module xSQLServer @@ -30,12 +45,12 @@ Get-DscResource -Module xSQLServer ## Requirements -The minimum PowerShell version required is 4.0, which ships in Windows 8.1 or Windows Server 2012R2 (or higher versions). But PowerShell 4.0 can also be installed on Windows Server 2008 R2. -The preferred version is PowerShell 5.0 or higher, which ships with Windows 10 or Windows Server 2016. +The minimum Windows Management Framework (PowerShell) version required is 4.0, which ships in Windows 8.1 or Windows Server 2012 R2 (or higher versions). But Windows Management Framework (PowerShell) 4.0 can also be installed on Windows Server 2008 R2. +The preferred Windows Management Framework (PowerShell) version is 5.0 or higher, which ships with Windows 10 or Windows Server 2016, but can also be installed on Windows 7 SP1, Windows 8.1, Windows Server 2008 R2 SP1, Windows Server 2012 and Windows Server 2012 R2. ## Examples -You can review the "examples" directory in the xSQLServer module for some general use scenarios for all of the resources that are in the module. +You can review the [Examples](/Examples) directory in the xSQLServer module for some general use scenarios for all of the resources that are in the module. ## Change log @@ -43,361 +58,812 @@ A full list of changes in each version can be found in the [change log](CHANGELO ## Resources -* **xSQLServerSetup** installs a standalone SQL Server instance -* **xSQLServerFirewall** configures firewall settings to allow remote access to a SQL Server instance. -* **xSQLServerRSSecureConnectionLevel** sets the secure connection level for SQL Server Reporting Services. -* **xSQLServerFailoverClusterSetup** installs SQL Server failover cluster instances. -* **xSQLServerRSConfig** configures SQL Server Reporting Services to use a database engine in another instance. -* **xSQLServerLogin** resource to manage SQL logins -* **xSQLServerRole** resource to manage SQL server roles -* **xSQLServerDatabaseRole** resource to manage SQL database roles -* **xSQLServerDatabasePermissions** resource to manage SQL database permissions -* **xSQLServerDatabaseOwner** resource to manage SQL database owners -* **xSQLDatabaseRecoveryModel** resource to manage database recovery model -* **xSQLServerMaxDop** resource to manage MaxDegree of Parallelism for SQL Server -* **xSQLServerMemory** resource to manage Memory for SQL Server -* **xSQLServerNetwork** resource to manage SQL Server Network Protocols -* **xSQLServerDatabase** resource to manage ensure database is present or absent -* **xSQLAOGroupEnsure** resource to ensure availability group is present or absent -* **xSQLAOGroupJoin** resource to join a replica to an existing availability group -* **xSQLServerAlwaysOnService** resource to enable always on on a SQL Server -* **xSQLServerEndpoint** resource to ensure database endpoint is present or absent -* **xWaitForAvailabilityGroup** resource to wait till availability group is created on primary server -* **xSQLServerConfiguration** resource to manage [SQL Server Configuration Options](https://msdn.microsoft.com/en-us/library/ms189631.aspx) -* **xSQLServerPermission** Grant or revoke permission on the SQL Server. -* **xSQLServerEndpointState** Change state of the endpoint. -* **xSQLServerEndpointPermission** Grant or revoke permission on the endpoint. -* **xSQLServerAvailabilityGroupListener** Create or remove an availability group listener. -* **xSQLServerReplication** resource to manage SQL Replication distribution and publishing. -* **xSQLServerScript** resource to extend DSCs Get/Set/Test functionality to T-SQL -* **xSQLServerAlias** resource to manage SQL Server client Aliases +* [**xSQLAOGroupEnsure**](#xsqlaogroupensure) resource to ensure availability group is present or absent +* [**xSQLAOGroupJoin**](#xsqlaogroupjoin) resource to join a replica to an existing availability group +* [**xSQLServerAlias**](#xsqlserveralias) resource to manage SQL Server client Aliases +* [**xSQLServerAlwaysOnService**](#xsqlserveralwaysonservice) resource to enable always on on a SQL Server +* [**xSQLServerAvailabilityGroupListener**](#xsqlserveravailabilitygrouplistener) Create or remove an availability group listener. +* [**xSQLServerConfiguration**](#xsqlserverconfiguration) resource to manage [SQL Server Configuration Options](https://msdn.microsoft.com/en-us/library/ms189631.aspx) +* [**xSQLServerDatabase**](#xsqlserverdatabase) resource to manage ensure database is present or absent +* [**xSQLServerDatabaseOwner**](#xsqlserverdatabaseowner) resource to manage SQL database owners +* [**xSQLServerDatabasePermission**](#xsqlserverdatabasepermission) resource to manage SQL database permissions +* [**xSQLServerDatabaseRecoveryModel**](#xsqlserverdatabaserecoverymodel) resource to manage database recovery model +* [**xSQLServerDatabaseRole**](#xsqlserverdatabaserole) resource to manage SQL database roles +* [**xSQLServerEndpoint**](#xsqlserverendpoint) resource to ensure database endpoint is present or absent +* [**xSQLServerEndpointPermission**](#xsqlserverendpointpermission) Grant or revoke permission on the endpoint. +* [**xSQLServerEndpointState**](#xsqlserverendpointstate) Change state of the endpoint. +* [**xSQLServerFailoverClusterSetup**](#xsqlserverfailoverclustersetup) installs SQL Server failover cluster instances. +* [**xSQLServerFirewall**](#xsqlserverfirewall) configures firewall settings to allow remote access to a SQL Server instance. +* [**xSQLServerLogin**](#xsqlserverlogin) resource to manage SQL logins +* [**xSQLServerMaxDop**](#xsqlservermaxdop) resource to manage MaxDegree of Parallelism for SQL Server +* [**xSQLServerMemory**](#xsqlservermemory) resource to manage Memory for SQL Server +* [**xSQLServerNetwork**](#xsqlservernetwork) resource to manage SQL Server Network Protocols +* [**xSQLServerPermission**](#xsqlserverpermission) Grant or revoke permission on the SQL Server. +* [**xSQLServerRole**](#xsqlserverrole) resource to manage SQL server roles +* [**xSQLServerReplication**](#xsqlserverreplication) resource to manage SQL Replication distribution and publishing. +* [**xSQLServerRSConfig**](#xsqlserverrsconfig) configures SQL Server Reporting Services to use a database engine in another instance. +* [**xSQLServerRSSecureConnectionLevel**](#xsqlserverrssecureconnectionlevel) sets the secure connection level for SQL Server Reporting Services. +* [**xSQLServerScript**](#xsqlserverscript) resource to extend DSCs Get/Set/Test functionality to T-SQL +* [**xSQLServerSetup**](#xsqlserversetup) installs a standalone SQL Server instance +* [**xWaitForAvailabilityGroup**](#xwaitforavailabilitygroup) resource to wait till availability group is created on primary server -### xSQLServerSetup +### xSQLAOGroupEnsure -* **SourcePath**: (Required) The path to the root of the source files for installation. I.e and UNC path to a shared resource. -* **SourceFolder**: Folder within the source path containing the source files for installation. Default value is 'Source'. -* **SetupCredential**: (Required) Credential to be used to perform the installation. -* **SourceCredential**: Credential used to access SourcePath. -* **SuppressReboot**: Suppresses reboot. -* **ForceReboot**: Forces reboot. -* **Features**: (Key) SQL features to be installed. -* **InstanceName**: (Key) SQL instance to be installed. -* **InstanceID**: SQL instance ID, if different from InstanceName. -* **PID**: Product key for licensed installations. -* **UpdateEnabled**: Enabled updates during installation. -* **UpdateSource**: Path to the source of updates to be applied during installation. -* **SQMReporting**: Enable customer experience reporting. -* **ErrorReporting**: Enable error reporting. -* **InstallSharedDir**: Installation path for shared SQL files. -* **InstallSharedWOWDir**: Installation path for x86 shared SQL files. -* **InstanceDir**: Installation path for SQL instance files. -* **SQLSvcAccount**: Service account for the SQL service. -* **SQLSvcAccountUsername**: (Read) Output user name for the SQL service. -* **AgtSvcAccount**: Service account for the SQL Agent service. -* **AgtSvcAccountUsername**: (Read) Output user name for the SQL Agent service. -* **SQLCollation**: Collation for SQL. -* **SQLSysAdminAccounts**: Array of accounts to be made SQL administrators. -* **SecurityMode**: Security mode to apply to the SQL Server instance. -* **SAPwd**: SA password, if SecurityMode is set to 'SQL'. -* **InstallSQLDataDir**: Root path for SQL database files. -* **SQLUserDBDir**: Path for SQL database files. -* **SQLUserDBLogDir**: Path for SQL log files. -* **SQLTempDBDir**: Path for SQL TempDB files. -* **SQLTempDBLogDir**: Path for SQL TempDB log files. -* **SQLBackupDir**: Path for SQL backup files. -* **FTSvcAccount**: Service account for the Full Text service. -* **FTSvcAccountUsername**: (Read) Output username for the Full Text service. -* **RSSvcAccount**: Service account for Reporting Services service. -* **RSSvcAccountUsername**: (Read) Output username for the Reporting Services service. -* **ASSvcAccount**: Service account for Analysis Services service. -* **ASSvcAccountUsername**: (Read) Output username for the Analysis Services service. -* **ASCollation**: Collation for Analysis Services. -* **ASSysAdminAccounts**: Array of accounts to be made Analysis Services admins. -* **ASDataDir**: Path for Analysis Services data files. -* **ASLogDir**: Path for Analysis Services log files. -* **ASBackupDir**: Path for Analysis Services backup files. -* **ASTempDir**: Path for Analysis Services temp files. -* **ASConfigDir**: Path for Analysis Services config. -* **ISSvcAccount**: Service account for Integration Services service. -* **ISSvcAccountUsername**: (Read) Output user name for the Integration Services service. -* **BrowserSvcStartupType**: Specifies the startup mode for SQL Server Browser service. Valid values are 'Automatic', 'Disabled' or 'Manual'. +No description. -### xSQLServerFirewall +#### Requirements -* **Ensure**: (Key) Ensures that SQL firewall rules are **Present** or **Absent** on the machine. -* **SourcePath**: (Required) UNC path to the root of the source files for installation. -* **SourceFolder**: Folder within the source path containing the source files for installation. -* **Features**: (Key) SQL features to enable firewall rules for. -* **InstanceName**: (Key) SQL instance to enable firewall rules for. -* **DatabaseEngineFirewall**: Is the firewall rule for the Database Engine enabled? -* **BrowserFirewall**: Is the firewall rule for the Browser enabled? -* **ReportingServicesFirewall**: Is the firewall rule for Reporting Services enabled? -* **AnalysisServicesFirewall**: Is the firewall rule for Analysis Services enabled? -* **IntegrationServicesFirewall**: Is the firewall rule for the Integration Services enabled? +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2012 or later. +* Target machine must have access to the Active Directory module. -### xSQLServerRSSecureConnectionLevel +#### Security Requirements -* **InstanceName**: (Key) SQL instance to set secure connection level for. -* **SecureConnectionLevel**: (Key) SQL Server Reporting Service secure connection level. -* **Credential**: (Required) Credential with administrative permissions to the SQL instance. +* The credentials provided in the parameter `SetupCredential` must have the right **Create Computer Object** in the origanization unit (OU) in which the Cluster Name Object (CNO) resides. -### xSQLServerFailoverClusterSetup +#### Parameters -* **Action**: (Key) { Prepare | Complete } -* **SourcePath**: (Required) UNC path to the root of the source files for installation. -* **SourceFolder**: Folder within the source path containing the source files for installation. -* **SetupCredential**: (Required) Credential to be used to perform the installation. -* **SourceCredential**: Credential to be used to access SourcePath -* **SuppressReboot**: Suppresses reboot -* **ForceReboot**: Forces Reboot -* **Features**: (Required) SQL features to be installed. -* **InstanceName**: (Key) SQL instance to be installed. -* **InstanceID**: SQL instance ID, if different from InstanceName. -* **PID**: Product key for licensed installations. -* **UpdateEnabled**: Enabled updates during installation. -* **UpdateSource**: Source of updates to be applied during installation. -* **SQMReporting**: Enable customer experience reporting. -* **ErrorReporting**: Enable error reporting. -* **FailoverClusterGroup**: Name of the resource group to be used for the SQL Server failover cluster. -* **FailoverClusterNetworkName**: (Required) Network name for the SQL Server failover cluster. -* **FailoverClusterIPAddress**: IPv4 address for the SQL Server failover cluster. -* **InstallSharedDir**: Installation path for shared SQL files. -* **InstallSharedWOWDir**: Installation path for x86 shared SQL files. -* **InstanceDir**: Installation path for SQL instance files. -* **SQLSvcAccount**: Service account for the SQL service. -* **SQLSvcAccountUsername**: Output user name for the SQL service. -* **AgtSvcAccount**: Service account for the SQL Agent service. -* **AgtSvcAccountUsername**: Output user name for the SQL Agent service. -* **SQLCollation**: Collation for SQL. -* **SQLSysAdminAccounts**: Array of accounts to be made SQL administrators. -* **SecurityMode**: SQL security mode. -* **SAPwd**: SA password, if SecurityMode=SQL. -* **InstallSQLDataDir**: Root path for SQL database files. -* **SQLUserDBDir**: Path for SQL database files. -* **SQLUserDBLogDir**: Path for SQL log files. -* **SQLTempDBDir**: Path for SQL TempDB files. -* **SQLTempDBLogDir**: Path for SQL TempDB log files. -* **SQLBackupDir**: Path for SQL backup files. -* **ASSvcAccount**: Service account for Analysis Services service. -* **ASSvcAccountUsername**: Output user name for the Analysis Services service. -* **ASCollation**: Collation for Analysis Services. -* **ASSysAdminAccounts**: Array of accounts to be made Analysis Services admins. -* **ASDataDir**: Path for Analysis Services data files. -* **ASLogDir**: Path for Analysis Services log files. -* **ASBackupDir**: Path for Analysis Services backup files. -* **ASTempDir**: Path for Analysis Services temp files. -* **ASConfigDir**: Path for Analysis Services config. -* **ISSvcAccount**: Service account for Integration Services service. -* **ISSvcAccountUsername**: Output user name for the Integration Services service. -* **ISFileSystemFolder**: File system folder for Integration Services. +* **[String] Ensure** _(Key)_: Determines whether the availability group should be added or removed. { Present | Absent }. +* **[String] AvailabilityGroupName**_(Key)_: Name for availability group. +* **[String] AvailabilityGroupNameListener** _(Write)_: Listener name for availability group. +* **[String[]] AvailabilityGroupNameIP** _(Write)_: List of IP addresses associated with listener. +* **[String[]] AvailabilityGroupSubMask** _(Write)_: Network subnetmask for listener. +* **[Unint32] AvailabilityGroupPort** _(Write)_: Port availability group should listen on. +* **[String] ReadableSecondary** _(Write)_: Mode secondaries should operate under (None, ReadOnly, ReadIntent). { None | *ReadOnly* | ReadIntent }. +* **[String] AutoBackupPreference** _(Write)_: Where backups should be backed up from (Primary, Secondary). { *Primary* | Secondary }. +* **[Uint32] BackupPriority** _(Write)_: The percentage weight for backup prority (default 50). +* **[Uint32] EndPointPort** _(Write)_: The TCP port for the SQL AG Endpoint (default 5022). +* **[String] SQLServer** _(Write)_: The SQL Server for the database. +* **[String] SQLInstance** _(Write)_: The SQL instance for the database. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to Grant Permissions on SQL Server, set this to $null to use Windows Authentication. -### xSQLServerRSConfig +#### Examples -* **InstanceName**: (Key) Name of the SQL Server Reporting Services instance to be configured. -* **RSSQLServer**: (Required) Name of the SQL Server to host the Reporting Service database. -* **RSSQLInstanceName**: (Required) Name of the SQL Server instance to host the Reporting Service database. -* **SQLAdminCredential**: (Required) Credential to be used to perform the configuration. -* **IsInitialized**: Output is the Reporting Services instance initialized. +None. -### xSQLServerLogin +### xSQLAOGroupJoin -* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. -* **Name**: (Key) The name of the SQL login. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. -* **LoginCredential**: If LoginType is 'SqlLogin' then a PSCredential is needed for the password to the login. -* **LoginType**: The SQL login type. Valid values are 'SqlLogin', 'WindowsUser' or 'WindowsGroup'. -* **SQLServer**: (Key) The SQL Server for the login. -* **SQLInstanceName**: (Key) The SQL instance for the login. +No description. -### xSQLServerRole +#### Requirements -* **Name**: (Key) Name of the SQL Login to create -* **Ensure**: If the values should be present or absent. Valid values are 'Present' or 'Absent'. -* **ServerRole**: Type of SQL role to add.(bulkadmin, dbcreator, diskadmin, processadmin , public, securityadmin, serveradmin , setupadmin, sysadmin) -* **SQLServer**: SQL Server where login should be created -* **SQLInstance**: (Key) SQL Instance for the login +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine2012 or later. -### xSQLServerDatabaseRole +#### Parameters -* **Ensure**: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). -* **Name**: (Key) The name of the login that will become a member, or removed as a member, of the role(s). -* **SQLServer**: (Key) The SQL server on which the instance exist. -* **SQLInstanceName**: (Key) The SQL instance in which the database exist. -* **Database**: (Key) The database in which the login (user) and role(s) exist. -* **Role**: One or more roles to which the login (user) will be added or removed. +* **[String] Ensure** _(Key)_: If the replica should be joined ('Present') to the Availability Group or not joined ('Absent') to the Availability Group. { Present | Absent }. +* **[String] AvailabilityGroupName** _(Key)_: The name Availability Group to join. +* **[String] SQLServer** _(Write)_: Name of the SQL server to be configured. +* **[String] SQLInstanceName** _(Write)_: Name of the SQL instance to be configured. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to Grant Permissions in SQL. -### xSQLServerDatabasePermissions +#### Examples -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of permissions for the SQL database -* **Permissions**: (Required) The set of Permissions for the SQL database -* **SQLServer**: The SQL Server for the database -* **SQLInstanceName**: The SQL instance for the database +None. -### xSQLServerDatabaseOwner +### xSQLServerAlias -* **Database**: (Key) The SQL Database -* **Name**: (Required) The name of the SQL login for the owner -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: The SQL instance for the database +No description. -### xSQLDatabaseRecoveryModel +#### Requirements -* **DatabaseName**: (key) The SQL database name -* **SQLServerInstance**: (Required) The SQL server and instance -* **RecoveryModel**: (Required) Recovery Model (Full, Simple, BulkLogged) +* Target machine must be running Windows Server 2008 R2. -### xSQLServerMaxDop +#### Parameters -* **Ensure**: An enumerated value that describes if Min and Max memory is configured -* **DyamicAlloc**: Flag to indicate if MaxDop is dynamically configured -* **MaxDop**: Numeric value to configure MaxDop to -* **SQLServer**: The SQL Server where to set MaxDop -* **SQLInstance** (Key): The SQL instance where to set MaxDop +* **[String] Name** _(Key)_: The name of Alias (e.g. svr01\inst01). +* **[String] ServerName** _(Key)_: The SQL Server you are aliasing (the netbios name or FQDN). +* **[String] Ensure** _(Write)_: Determines whether the alias should be added or removed. Default value is 'Present'. { *Present* | Absent }. +* **[String] Protocol** _(Write)_: Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes). Default value is 'TCP'. { *TCP* | NP }. +* **[Uint16] TCPPort** _(Write)_: The TCP port SQL is listening on. Only used when protocol is set to 'TCP'. Default value is port 1433. +* **[Boolean] UseDynamicTcpPort** _(Write)_: The UseDynamicTcpPort specify that the Net-Library will determine the port dynamically. The port specified in Port number will not be used. Default value is '$false'. -### xSQLServerMemory +#### Read-Only Properties from Get-TargetResource -* **Ensure**: An enumerated value that describes if Min and Max memory is configured -* **DyamicAlloc**: (key) Flag to indicate if Memory is dynamically configured -* **MinMemory**: Minimum memory value to set SQL Server memory to -* **MaxMemory**: Maximum memory value to set SQL Server memory to -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: (key) The SQL instance for the database +* **[String] PipeName** _(Read)_: Named Pipes path from the Get-TargetResource method. -### xSQLServerNetwork +#### Examples + +* [Add an SQL Server alias](/Examples/Resources/xSQLServerAlias/1-AddSQLServerAlias.ps1) +* [Remove an SQL Server alias](/Examples/Resources/xSQLServerAlias/2-RemoveSQLServerAlias.ps1) + +### xSQLServerAlwaysOnService + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2012 or later. + +#### Parameters + +* **[String] SQLServer** _(Key)_: The hostname of the SQL Server to be configured. +* **[String] SQLInstance** _(Key)_: Name of the SQL instance to be configured. +* **[String] Ensure** _(Required)_: An enumerated value that describes if SQL server should have AlwaysOn property present or absent. { Present | Absent }. +* **[Sint32] RestartTimeout** _(Write)_: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. + +#### Examples + +None. -* **InstanceName**: (Key) name of SQL Server instance for which network will be configured. -* **ProtocolName**: (Required) Name of network protocol to be configured. Only tcp is currently supported. -* **IsEnabled**: Enables/Disables network protocol. -* **TCPDynamicPorts**: 0 if Dynamic ports should be used otherwise empty. -* **TCPPort**: Custom TCP port. -* **RestartService**: If true will restart SQL Service instance service after update. Default false. +### xSQLServerAvailabilityGroupListener + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2012 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. +* Requires that the Cluster name Object (CNO) has been delegated the right _Create Computer Object_ in the organizational unit (OU) in which the Cluster Name Object (CNO) resides. + +#### Parameters + +* **[String] InstanceName** _(Key)_: The SQL Server instance name of the primary replica. +* **[String] AvailabilityGroup** _(Key)_: The name of the availability group to which the availability group listener is or will be connected. +* **[String] NodeName** _(Write)_: The host name or FQDN of the primary replica. +* **[String] Ensure** _(Write)_: If the availability group listener should be present or absent. { Present | Absent }. +* **[String] Name** _(Write)_: The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). +* **[String[]] IpAddress** _(Write)_: The IP address used for the availability group listener, in the format 192.168.10.45/255.255.252.0. If using DCHP, set to the first IP-address of the DHCP subnet, in the format 192.168.8.1/255.255.252.0. Must be valid in the cluster-allowed IP range. +* **[Uint16] Port** _(Write)_: The port used for the availability group listener. +* **[Boolean] DHCP** _(Write)_: If DHCP should be used for the availability group listener instead of static IP address. + +#### Examples + +None. + +### xSQLServerConfiguration + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLServer** _(Key)_: The hostname of the SQL Server to be configured +* **[String] OptionName** _(Key)_: The name of the SQL configuration option to be checked. For all possible values reference [MSDN](https://msdn.microsoft.com/en-us/library/ms189631.aspx) or run sp_configure. +* **[Sint32] OptionValue** _(Required)_: The desired value of the SQL configuration option +* **[String] SQLInstanceName** _(Write)_: Name of the SQL instance to be configured. Default is 'MSSQLSERVER' +* **[Boolean] RestartService** _(Write)_: Determines whether the instance should be restarted after updating the configuration option +* **[Sint32] RestartTimeout** _(Write)_: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. + +#### Examples + +None. ### xSQLServerDatabase -* **Database**: (key) Database to be created or dropped -* **Ensure**: (Default = 'Present') An enumerated value that describes if Database is to be present or absent. -* **SQLServer**: (key) The SQL Server for the database -* **SQLInstance**: (key) The SQL instance for the database +No description. -### xSQLAOGroupEnsure +#### Requirements -* **Ensure**: (Key) Determines whether the availability group should be added or removed. -* **AvailabilityGroupName** (Key) Name for availability group. -* **AvailabilityGroupNameListener** Listener name for availability group. -* **AvailabilityGroupNameIP** List of IP addresses associated with listener. -* **AvailabilityGroupSubMask** Network subnetmask for listener. -* **AvailabilityGroupPort** Port availability group should listen on. -* **ReadableSecondary** Mode secondaries should operate under (None, ReadOnly, ReadIntent). -* **AutoBackupPreference** Where backups should be backed up from (Primary, Secondary). -* **BackupPriority** The percentage weight for backup prority (default 50). -* **EndPointPort** The TCP port for the SQL AG Endpoint (default 5022). -* **SQLServer**: The SQL Server for the database. -* **SQLInstance**: The SQL instance for the database. -* **SetupCredential**: (Required) Credential to be used to Grant Permissions on SQL Server, set this to $null to use Windows Authentication. +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. -### xSQLAOGroupJoin +#### Parameters -* **Ensure**: (key) If the replica should be joined ('Present') to the Availability Group or not joined ('Absent') to the Availability Group. -* **AvailabilityGroupName** (key) The name Availability Group to join. -* **SQLServer**: Name of the SQL server to be configured. -* **SQLInstanceName**: Name of the SQL instance to be configured. -* **SetupCredential**: (Required) Credential to be used to Grant Permissions in SQL. +* **[String] SQLServer** _(Key)_: The SQL Server for the database +* **[String] SQLInstanceName** _(Key)_: The SQL instance for the database +* **[String] Name** _(Key)_: Database to be created or dropped +* **[String] Ensure** _(Write)_: If the values should be present or absent. Valid values are 'Present' or 'Absent'. Default Value is 'Present'. { *Present* | Absent }. -### xSQLServerAlwaysOnService +#### Examples + +None. + +### xSQLServerDatabaseOwner + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Database** _(Key)_: The SQL Database +* **[String] Name** _(Required)_: The name of the SQL login for the owner +* **[String] SQLServer** _(Write)_: The SQL Server for the database +* **[String] SQLInstance** _(Write)_: The SQL instance for the database -* **Ensure**: (Required) An enumerated value that describes if SQL server should have AlwaysOn property present or absent. -* **SQLServer**: (Key) The hostname of the SQL Server to be configured. -* **SQLInstance**: (Key) Name of the SQL instance to be configured. -* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. +#### Examples + +* [Set database owner](/Examples/Resources/xSQLServerDatabaseOwner/1-SetDatabaseOwner.ps1) + +### xSQLServerDatabasePermission + +This resource is used to grant, deny or revoke permissions for a user in a database. +For more information about permissions, please read the article [Permissions (Database Engine)](https://msdn.microsoft.com/en-us/library/ms191291.aspx). + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Ensure** _(Write)_: If the permission should be granted (Present) or revoked (Absent). { Present | Absent }. +* **[String] Database** _(Key)_: The name of the database. +* **[String] Name** _(Key)_: The name of the user that should be granted or denied the permission. +* **[String[]] Permissions** _(Required)_: The permissions to be granted or denied for the user in the database. Valid permissions can be found in the article [SQL Server Permissions](https://msdn.microsoft.com/en-us/library/ms191291.aspx#Anchor_3). +* **[String] PermissionState** _(Key)_: The state of the permission. { Grant | Deny }. +* **[String] SQLServer** _(Key)_: The host name of the SQL Server to be configured. Default values is 'env:COMPUTERNAME'. +* **[String] SQLInstanceName** _(Key)_: The name of the SQL instance to be configured. Default value is 'MSSQLSERVER'. + +#### Examples + +* [Grant Database Permission](/Examples/Resources/xSQLServerDatabasePermission/1-GrantDatabasePermissions.ps1) +* [Revoke Database Permission](/Examples/Resources/xSQLServerDatabasePermission/2-RevokeDatabasePermissions.ps1) +* [Deny Database Permission](/Examples/Resources/xSQLServerDatabasePermission/3-DenyDatabasePermissions.ps1) + +### xSQLServerDatabaseRecoveryModel + +This resource set the recovery model for a database. The recovery model controls how transactions are logged, whether the transaction log requires (and allows) backing up, and what kinds of restore operations are available. +Three recovery models exist: full, simple, and bulk-logged. +Read more about recovery model in this article [View or Change the Recovery Model of a Database](https://msdn.microsoft.com/en-us/library/ms189272.aspx) + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Name** _(Key)_: The SQL database name. +* **[String] SQLServer** _(Key)_: The host name of the SQL Server to be configured. +* **[String] SQLInstanceName** _(Key)_: The name of the SQL instance to be configured. +* **[String] RecoveryModel** _(Required)_: The recovery model to use for the database. { Full | Simple | BulkLogged }. + +#### Examples + +* [Set the RecoveryModel of a database](/Examples/Resources/xSQLServerDatabaseRecoveryModel/1-SetDatabaseRecoveryModel.ps1) + +### xSQLServerDatabaseRole + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] Name** _(Key)_: The name of the login that will become a member, or removed as a member, of the role(s). +* **[String] SQLServer** _(Key)_: The SQL server on which the instance exist. +* **[String] SQLInstanceName** _(Key)_: The SQL instance in which the database exist. +* **[String] Database** _(Key)_: The database in which the login (user) and role(s) exist. +* **[String] Ensure** _(Write)_: If 'Present' (the default value) then the login (user) will be added to the role(s). If 'Absent' then the login (user) will be removed from the role(s). { *Present* | Absent }. +* **[String[]] Role**_(Required): One or more roles to which the login (user) will be added or removed. + +#### Examples + +None. ### xSQLServerEndpoint -* **EndPointName**: Name for endpoint to be created on SQL Server -* **Ensure**: (key) An enumerated value that describes if endpoint is to be present or absent on SQL Server -* **Port**: Port Endpoint should listen on -* **AuthorizedUser**: User who should have connect ability to endpoint -* **SQLServer**: The SQL Server for the database -* **SQLInstance**: The SQL instance for the database +No description. -### xWaitforAvailabilityGroup +#### Requirements -* **Name**: (key) Name for availability group -* **RetryIntervalSec**: Interval to check for availability group -* **RetryCount**: Maximum number of retries to check availability group creation +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. -### xSQLServerConfiguration +#### Security Requirements -* **SQLServer**: (Key) The hostname of the SQL Server to be configured -* **SQLInstanceName**: (Write) Name of the SQL instance to be configured. Default is 'MSSQLSERVER' -* **OptionName**: (Key) The name of the SQL configuration option to be checked. For all possible values reference [MSDN](https://msdn.microsoft.com/en-us/library/ms189631.aspx) or run sp_configure. -* **OptionValue**: (Required) The desired value of the SQL configuration option -* **RestartService**: Determines whether the instance should be restarted after updating the configuration option -* **RestartTimeout**: The length of time, in seconds, to wait for the service to restart. Default is 120 seconds. +* The built-in parameter `PsDscRunAsCredential` must be set to the credentials of an account with the permission to enumerate logins, create the endpoint, and alter the permission on an endpoint. -### xSQLServerPermission +#### Parameters -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Ensure** If the permission should be present or absent. -* **Principal** The login to which permission will be set. -* **Permission** The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. +* **[String] EndPointName** _(Key)_: Name for endpoint to be created on SQL Server +* **[String] Ensure** _(Write)_: An enumerated value that describes if endpoint is to be present or absent on SQL Server. { Present | Absent }. +* **[Uint32] Port** _(Write)_: Port Endpoint should listen on +* **[String] AuthorizedUser** _(Write)_: User who should have connect ability to endpoint +* **[String] SQLServer** _(Write)_: The SQL Server for the database +* **[String] SQLInstance** _(Write)_: The SQL instance for the database -### xSQLServerEndpointState +#### Examples -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Name** The name of the endpoint. -* **State** The state of the endpoint. Valid states are Started, Stopped or Disabled. +None. ### xSQLServerEndpointPermission -* **InstanceName** The SQL Server instance name. -* **NodeName** The host name or FQDN. -* **Ensure** If the permission should be present or absent. -* **Name** The name of the endpoint. -* **Principal** The login to which permission will be set. -* **Permission** The permission to set for the login. Valid value for permission are only CONNECT. +No description. -### xSQLServerAvailabilityGroupListener +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + +#### Parameters + +* **[String] InstanceName** _(Key)_: The SQL Server instance name. +* **[String] NodeName** _(Required)_: The host name or FQDN. +* **[String] Ensure** _(Write)_: If the permission should be present or absent. { Present | Absent }. +* **[String] Name** _(Required)_: The name of the endpoint. +* **[String] Principal** _(Key)_: The login to which permission will be set. +* **[String] Permission** _(Write)_: The permission to set for the login. Valid value for permission are only CONNECT. { Connect }. + +#### Examples + +None. + +### xSQLServerEndpointState + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + +#### Parameters + +* **[String] InstanceName** _(Key)_: The SQL Server instance name. +* **[String] NodeName** _(Required)_: The host name or FQDN. +* **[String] Name** _(Required)_: The name of the endpoint. +* **[String] State** _(Write)_: The state of the endpoint. Valid states are Started, Stopped or Disabled. { Started | Stopped | Disabled }. + +#### Examples + +None. + +### xSQLServerFailoverClusterSetup + +**This resource is deprecated.** The functionality of this resource has been merged with [xSQLServerSetup](#xsqlserversetup). Please do not use this resource for new development efforts. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 R2 or later. + +#### Parameters + +* **[String] Action** _(Key)_: Prepare or Complete. { Prepare | Complete }. +* **[String] InstanceName** _(Key)_: SQL instance to be installed. +* **[String] Features** _(Required)_: SQL features to be installed. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to perform the installation. +* **[String] FailoverClusterNetworkName** _(Required)_: Network name for the SQL Server failover cluster. +* **[PSCredential] SQLSvcAccount** _(Required)_: Service account for the SQL service. +* **[String] SourcePath** _(Write)_: UNC path to the root of the source files for installation. +* **[String] SourceFolder** _(Write)_: Folder within the source path containing the source files for installation. +* **[PSCredential] SourceCredential** _(Write)_: Credential to be used to access SourcePath +* **[Boolean] SuppressReboot** _(Write)_: Suppresses reboot +* **[Boolean] ForceReboot** _(Write)_: Forces Reboot +* **[String] InstanceID** _(Write)_: SQL instance ID, if different from InstanceName. +* **[String] PID** _(Write)_: Product key for licensed installations. +* **[String] UpdateEnabled** _(Write)_: Enabled updates during installation. +* **[String] UpdateSource** _(Write)_: Source of updates to be applied during installation. +* **[String] SQMReporting** _(Write)_: Enable customer experience reporting. +* **[String] ErrorReporting** _(Write)_: Enable error reporting. +* **[String] FailoverClusterGroup** _(Write)_: Name of the resource group to be used for the SQL Server failover cluster. +* **[String] FailoverClusterIPAddress** _(Write)_: IPv4 address for the SQL Server failover cluster. +* **[String] InstallSharedDir** _(Write)_: Installation path for shared SQL files. +* **[String] InstallSharedWOWDir** _(Write)_: Installation path for x86 shared SQL files. +* **[String] InstanceDir** _(Write)_: Installation path for SQL instance files. +* **[PSCredential] AgtSvcAccount** _(Write)_: Service account for the SQL Agent service. +* **[String] SQLCollation** _(Write)_: Collation for SQL. +* **[String[]] SQLSysAdminAccounts** _(Write)_: Array of accounts to be made SQL administrators. +* **[String] SecurityMode** _(Write)_: SQL security mode. +* **[PSCredential] SAPwd** _(Write)_: SA password, if SecurityMode=SQL. +* **[String] InstallSQLDataDir** _(Write)_: Root path for SQL database files. +* **[String] SQLUserDBDir** _(Write)_: Path for SQL database files. +* **[String] SQLUserDBLogDir** _(Write)_: Path for SQL log files. +* **[String] SQLTempDBDir** _(Write)_: Path for SQL TempDB files. +* **[String] SQLTempDBLogDir** _(Write)_: Path for SQL TempDB log files. +* **[String] SQLBackupDir** _(Write)_: Path for SQL backup files. +* **[PSCredential] ASSvcAccount** _(Write)_: Service account for Analysis Services service. +* **[String] ASCollation** _(Write)_: Collation for Analysis Services. +* **[String[]] ASSysAdminAccounts** _(Write)_: Array of accounts to be made Analysis Services admins. +* **[String] ASDataDir** _(Write)_: Path for Analysis Services data files. +* **[String] ASLogDir** _(Write)_: Path for Analysis Services log files. +* **[String] ASBackupDir** _(Write)_: Path for Analysis Services backup files. +* **[String] ASTempDir** _(Write)_: Path for Analysis Services temp files. +* **[String] ASConfigDir** _(Write)_: Path for Analysis Services config. +* **[PSCredential] ISSvcAccount** _(Write)_: Service account for Integration Services service. +* **[String] ISFileSystemFolder** _(Write)_: File system folder for Integration Services. + +#### Read-Only Properties from Get-TargetResource + +* **[String] SQLSvcAccountUsername** _(Read)_: Output user name for the SQL service. +* **[String] AgtSvcAccountUsername** _(Read)_: Output user name for the SQL Agent service. +* **[String] ASSvcAccountUsername** _(Read)_: Output user name for the Analysis Services service. +* **[String] ISSvcAccountUsername** _(Read)_: Output user name for the Integration Services service. + +#### Examples + +None. + +### xSQLServerFirewall + +This will set default firewall rules for the supported features. Currently the features supported are Database Engine, +Analysis Services, SQL Browser, SQL Reporting Services and Integration Services. + +#### Firewall rules + +##### Default rules for default instance + +| Feature | Component | Enable Firewall Rule | Firewall Name | +| --- | --- | --- | --- | +| SQLENGINE | Database Engine | Application: sqlservr.exe | SQL Server Database Engine instance MSSQLSERVER | +| SQLENGINE | Database Engine | Service: SQLBrowser | SQL Server Browser | +| AS | Analysis Services | Service: MSSQLServerOLAPService | SQL Server Analysis Services instance MSSQLSERVER | +| AS | Analysis Services | Service: SQLBrowser | SQL Server Browser | +| RS | Reporting Services | Port: tcp/80 | SQL Server Reporting Services 80 | +| RS | Reporting Services | Port: tcp/443 | SQL Server Reporting Services 443 | +| IS | Integration Services | Application: MsDtsSrvr.exe | SQL Server Integration Services Application | +| IS | Integration Services | Port: tcp/135 | SQL Server Integration Services Port | + +##### Default rules for named instance -*This resource requires that the CNO has been delegated the right `Create computer object` on the organizational unit (OU) in which the CNO resides.* +| Feature | Component | Enable Firewall Rule | Firewall Name | +| --- | --- | --- | --- | +| SQLENGINE | Database Engine | Application: sqlservr.exe | SQL Server Database Engine instance \ | +| SQLENGINE | Database Engine | Service: SQLBrowser | SQL Server Browser | +| AS | Analysis Services | Service: MSOLAP$INSTANCE | | SQL Server Analysis Services instance \ | +| AS | Analysis Services | Service: SQLBrowser | SQL Server Browser | +| RS | Reporting Services | Port: tcp/80 | SQL Server Reporting Services 80 | +| RS | Reporting Services | Port: tcp/443 | SQL Server Reporting Services 443 | +| IS | Integration Services | Application: MsDtsSrvr.exe | SQL Server Integration Services Application | +| IS | Integration Services | Port: tcp/135 | SQL Server Integration Services Port | -* **InstanceName** The SQL Server instance name of the primary replica. -* **NodeName** The host name or FQDN of the primary replica. -* **Ensure** If the availability group listener should be present or absent. -* **Name** The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). -* **AvailabilityGroup** The name of the availability group to which the availability group listener is or will be connected. -* **IpAddress** The IP address used for the availability group listener, in the format 192.168.10.45/255.255.252.0. If using DCHP, set to the first IP-address of the DHCP subnet, in the format 192.168.8.1/255.255.252.0. Must be valid in the cluster-allowed IP range. -* **Port** The port used for the availability group listener. -* **DHCP** If DHCP should be used for the availability group listener instead of static IP address. +#### Requirements + +* Target machine must be running Windows Server 2008 R2. + +#### Parameters + +* **[String] Features** _(Key)_: SQL features to enable firewall rules for. +* **[String] InstanceName** _(Key)_: SQL instance to enable firewall rules for. +* **[String] Ensure** _(Write)_: Ensures that SQL firewall rules are **Present** or **Absent** on the machine. { *Present* | Absent }. +* **[String] SourcePath** _(Write)_: UNC path to the root of the source files for installation. +* **[String] SourceCredential** _(Write)_: Credentials used to access the path set in the parameter 'SourcePath'. This parmeter is optional either if built-in parameter 'PsDscRunAsCredential' is used, or if the source path can be access using the SYSTEM account. + +#### Read-Only Properties from Get-TargetResource + +* **[Boolean] DatabaseEngineFirewall** _(Read)_: Is the firewall rule for the Database Engine enabled? +* **[Boolean] BrowserFirewall** _(Read)_: Is the firewall rule for the Browser enabled? +* **[Boolean] ReportingServicesFirewall** _(Read)_: Is the firewall rule for Reporting Services enabled? +* **[Boolean] AnalysisServicesFirewall** _(Read)_: Is the firewall rule for Analysis Services enabled? +* **[Boolean] IntegrationServicesFirewall** _(Read)_: Is the firewall rule for the Integration Services enabled? + +#### Examples + +* [Create inbound firewall rules](/Examples/Resources/xSQLServerFirewall/1-CreateInboundFirewallRules.ps1) +* [Remove inbound firewall rules](/Examples/Resources/xSQLServerFirewall/2-RemoveInboundFirewallRules.ps1) + +### xSQLServerLogin + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLServer** _(Key)_:The hostname of the SQL Server to be configured. +* **[String] SQLInstanceName** _(Key)_: Name of the SQL instance to be configured. +* **[String] Name** _(Key)_: The name of the login. +* **[String] Ensure** _(Write)_: The specified login is Present or Absent. { *Present* | Absent }. +* **[PSCredential] LoginCredential** _(Write)_: If LoginType is 'SqlLogin' then a PSCredential is needed for the password to the login. +* **[String] LoginType** _(Write)_: The type of login to be created. If LoginType is 'WindowsUser' or 'WindowsGroup' then provide the name in the format DOMAIN\name. Default is WindowsUser. Unsupported login types are Certificate, AsymmetricKey, ExternalUser, and ExternalGroup. {SqlLogin | WindowsUser | WindowsGroup } +* **[Boolean] LoginMustChangePassword** _(Write)_: Specifies if the login is required to have its password change on the next login. Only applies to SQL Logins. Default is $true. +* **[Boolean] LoginPasswordExpirationEnabled** _(Write)_: Specifies if the login password is required to expire in accordance to the operating system security policy. Only applies to SQL Logins. Default is $true. +* **[Boolean] LoginPasswordPolicyEnforced** _(Write)_: Specifies if the login password is required to conform to the password policy specified in the system security policy. Only applies to SQL Logins. Default is $true. + +#### Examples + +None. + +### xSQLServerMaxDop + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLInstance** (Key): The SQL instance where to set MaxDop +* **[String] Ensure** _(Write)_: An enumerated value that describes if Min and Max memory is configured. { *Present* | Absent }. +* **[Boolean] DyamicAlloc** _(Write)_: Flag to indicate if MaxDop is dynamically configured +* **[Sint32] MaxDop** _(Write)_: Numeric value to configure MaxDop to +* **[String] SQLServer** _(Write)_: The SQL Server where to set MaxDop + +#### Examples + +None. + +### xSQLServerMemory + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLInstance** _(Key)_: The SQL instance for the database +* **[Boolean] DyamicAlloc** _(Key)_: Flag to indicate if Memory is dynamically configured +* **[String] Ensure** _(Write)_: An enumerated value that describes if Min and Max memory is configured. { *Present* | Absent }. +* **[Sint32] MinMemory** _(Write)_: Minimum memory value to set SQL Server memory to +* **[Sint32] MaxMemory** _(Write)_: Maximum memory value to set SQL Server memory to +* **[String] SQLServer** _(Write)_: The SQL Server for the database + +#### Examples + +None. + +### xSQLServerNetwork + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: name of SQL Server instance for which network will be configured. +* **[String] ProtocolName** _(Required)_: Name of network protocol to be configured. Only tcp is currently supported. { tcp }. +* **[Boolean] IsEnabled** _(Write)_: Enables/Disables network protocol. +* **[String] TCPDynamicPorts** _(Write)_: 0 if Dynamic ports should be used otherwise empty. { 0 }. +* **[String] TCPPort** _(Write)_: Custom TCP port. +* **[Boolean] RestartService** _(Write)_: If true will restart SQL Service instance service after update. Default false. + +#### Examples + +None. + +### xSQLServerPermission + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + +#### Parameters + +* **[String] InstanceName** _(Key)_: The SQL Server instance name. +* **[String] NodeName** _(Required)_: The host name or FQDN. +* **[String] Principal** _(Required)_: The login to which permission will be set. +* **[String] Ensure** _(Write)_: If the permission should be present or absent. { Present | Absent }. +* **[String[]] Permission** _(Write)_: The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. { AlterAnyAvailabilityGroup | AlterAnyEndPoint | ViewServerState }. + +#### Examples + +None. + +### xSQLServerRole + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **[String] SQLInstanceName** _(Key)_: SQL Instance for the login +* **[String] Name** _(Key)_: Name of the SQL Login to create +* **[String] SQLServer** _(Required)_: SQL Server where login should be created +* **[String[]] ServerRole** _(Required)_: Type of SQL role to add. { bulkadmin | dbcreator | diskadmin | processadmin | public | securityadmin | serveradmin | setupadmin | sysadmin }. +* **[String] Ensure** _(Write)_: If the values should be present or absent. Valid values are 'Present' or 'Absent'. { *Present* | Absent }. + +#### Examples + +* [Add a server role to a login](/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1) +* [Remove server role from a login](/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1) ### xSQLServerReplication -* **InstanceName**: (Key) SQL Server instance name where replication distribution will be configured. -* **Ensure**: (Default = 'Present') 'Present' will configure replication, 'Absent' will disable replication. -* **DistributorMode**: (Required), 'Local' - Instance will be configured as it's own distributor, 'Remote' - Instace will be configure with remote distributor (remote distributor needs to be already configured for distribution). -* **AdminLinkCredentials**: (Required) - AdminLink password to be used when setting up publisher distributor relationship. -* **DistributionDBName**: (Default = 'distribution') distribution database name. If DistributionMode='Local' this will be created, if 'Remote' needs to match distribution database on remote distributor. -* **RemoteDistributor**: (Required if DistributionMode='Remote') SQL Server network name that will be used as distributor for local instance. -* **WorkingDirectory**: (Required) Publisher working directory. -* **UseTrustedConnection**: (Default = $true) Publisher security mode. -* **UninstallWithForce**: (Default = $true) Force flag for uninstall procedure +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: SQL Server instance name where replication distribution will be configured. +* **[String] Ensure** _(Write)_: (Default = 'Present') 'Present' will configure replication, 'Absent' will disable replication. +* **[String] DistributorMode** _(Required)_: 'Local' - Instance will be configured as it's own distributor, 'Remote' - Instace will be configure with remote distributor (remote distributor needs to be already configured for distribution). +* **[PSCredentials] AdminLinkCredentials** _(Required)_: - AdminLink password to be used when setting up publisher distributor relationship. +* **[String] DistributionDBName** _(Write)_: (Default = 'distribution') distribution database name. If DistributionMode='Local' this will be created, if 'Remote' needs to match distribution database on remote distributor. +* **[String] RemoteDistributor** _(Write)_: (Required if DistributionMode='Remote') SQL Server network name that will be used as distributor for local instance. +* **[String] WorkingDirectory** _(Required)_: Publisher working directory. +* **[Boolean] UseTrustedConnection** _(Write)_: (Default = $true) Publisher security mode. +* **[Boolean] UninstallWithForce** _(Write)_: (Default = $true) Force flag for uninstall procedure + +#### Examples + +None. + +### xSQLServerRSConfig + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Reporting Services 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: Name of the SQL Server Reporting Services instance to be configured. +* **[String] RSSQLServer** _(Required)_: Name of the SQL Server to host the Reporting Service database. +* **[String] RSSQLInstanceName** _(Required)_: Name of the SQL Server instance to host the Reporting Service database. +* **[PSCredential] SQLAdminCredential** _(Required)_: Credential to be used to perform the configuration. + +#### Read-Only Properties from Get-TargetResource + +* **[Read] IsInitialized** _(Read)_: Output is the Reporting Services instance initialized. + +#### Examples + +None. + +### xSQLServerRSSecureConnectionLevel + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server Reporting Services 2008 or later. + +#### Parameters + +* **[String] InstanceName** _(Key)_: SQL instance to set secure connection level for. +* **[Uint16] SecureConnectionLevel** _(Key)_: SQL Server Reporting Service secure connection level. +* **[PSCredential] SQLAdminCredential** _(Required)_: Credential with administrative permissions to the SQL instance. + +#### Examples + +None. ### xSQLServerScript -* **ServerInstance**: (Required) The name of an instance of the Database Engine. For default instances, only specify the computer name. For named instances, use the format ComputerName\\InstanceName. -* **SetFilePath**: (Key) Path to SQL file that will perform Set action. -* **GetFilePath**: (Key) Path to SQL file that will perform Get action. SQL Queries returned by this function are returned by the Get-DscConfiguration cmdlet with the GetResult parameter. -* **TestFilePath**: (Key) Path to SQL file that will perform Test action. Any Script that does not throw an error and returns null is evaluated to true. Invoke-SqlCmd treats SQL Print statements as verbose text, this will not cause a Test to return false. -* **Credential**: Specifies the credentials for making a SQL Server Authentication connection to an instance of the Database Engine. -* **Variable**: Creates a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. +Provides the means to run a user generated T-SQL script on the SQL Server instance. Three scripts are required; Get T-SQL script, Set T-SQL script and the Test T-SQL script. -### xSQLServerAlias +| T-SQL Script | Description | +| --- | --- | +| Get | The Get T-SQL script is used to query the status when running the cmdlet Get-DscConfiguration, and the result can be found in the property `GetResult`. | +| Test | The Test T-SQL script is used to test if the desired state is met. If Test T-SQL raises an error or returns any value other than 'null' the test fails, thus the Set T-SQL script is run. | +| Set | The Set T-SQL script performs the actual change when Test T-SQL script fails. | + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. +* Target machine must be running SQL Server 2008 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer PowerShell module. + +_Note: There is a known problem running this resource using PowerShell 4.0. See [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) for more information._ + +#### Parameters + +* **[String] ServerInstance** _(Key)_: The name of an instance of the Database Engine. For a default instance, only specify the computer name. For a named instances, use the format ComputerName\\InstanceName. +* **[String] SetFilePath** _(Key)_: Path to the T-SQL file that will perform Set action. +* **[String] GetFilePath** _(Key)_: Path to the T-SQL file that will perform Get action. Any values returned by the T-SQL queries will also be returned by the cmdlet Get-DscConfiguration through the `GetResult` property. +* **[String] TestFilePath** _(Key)_: Path to the T-SQL file that will perform Test action. Any script that does not throw an error or returns null is evaluated to true. The cmdlet Invoke-SqlCmd treats T-SQL Print statements as verbose text, and will not cause the test to return false. +* **[PSCredential] Credential** _(Write)_: The credentials to authenticate with, using SQL Authentication. To authenticate using Windows Authentication, assign the credentials to the built-in parameter `PsDscRunAsCredential`. If both parameters `Credential` and `PsDscRunAsCredential` are not assigned, then SYSTEM account will be used to authenticate using Windows Authentication. +* **[String[]] Variable** _(Write)_: Specifies, as a string array, a sqlcmd scripting variable for use in the sqlcmd script, and sets a value for the variable. Use a Windows PowerShell array to specify multiple variables and their values. For more information how to use this, please go to the help documentation for [Invoke-Sqlcmd](https://technet.microsoft.com/en-us/library/mt683370.aspx). + +#### Read-Only Properties from Get-TargetResource + +* **GetResult** _(Read)_: Contains the values returned from the T-SQL script provided in the parameter `GetFilePath` when cmdlet Get-DscConfiguration is run. + +#### Examples + +* [Run a script using SQL Authentication](/Examples/Resources/xSQLServerScript/1-RunScriptUsingSQLAuthentication.ps1) +* [Run a script using Windows Authentication](/Examples/Resources/xSQLServerScript/2-RunScriptUsingWindowsAuthentication.ps1) + +### xSQLServerSetup + +Installs SQL Server on the target node. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. + +#### Parameters + +* **[String] Action** _(Write)_: The action to be performed. Defaults to 'Install'. { _Install_ | InstallFailoverCluster | AddNode | PrepareFailoverCluster | CompleteFailoverCluster } +* **[String] InstanceName** _(Key)_: SQL instance to be installed. +* **[PSCredential] SetupCredential** _(Required)_: Credential to be used to perform the installation. +* **[String] SourcePath** _(Write)_: The path to the root of the source files for installation. I.e and UNC path to a shared resource. Environment variables can be used in the path. +* **[PSCredential] SourceCredential** _(Write)_: Credentials used to access the path set in the parameter `SourcePath`. Using this parameter will trigger a copy of the installation media to a temp folder on the target node. Setup will then be started from the temp folder on the target node. For any subsequent calls to the resource, the parameter `SourceCredential` is used to evaluate what major version the file 'setup.exe' has in the path set, again, by the parameter `SourcePath`. To know how the temp folder is evaluated please read the online documentation for [System.IO.Path.GetTempPath()](https://msdn.microsoft.com/en-us/library/system.io.path.gettemppath(v=vs.110).aspx). If the path, that is assigned to parameter `SourcePath`, contains a leaf folder, for example '\\server\share\folder', then that leaf folder will be used as the name of the temporary folder. If the path, that is assigned to parameter `SourcePath`, does not have a leaf folder, for example '\\server\share', then a unique guid will be used as the name of the temporary folder. +* **[Boolean] SuppressReboot** _(Write)_: Suppresses reboot. +* **[Boolean] ForceReboot** _(Write)_: Forces reboot. +* **[String] Features** _(Write)_: SQL features to be installed. +* **[String] InstanceID** _(Write)_: SQL instance ID, if different from InstanceName. +* **[String] ProductKey** _(Write)_: Product key for licensed installations. +* **[String] UpdateEnabled** _(Write)_: Enabled updates during installation. +* **[String] UpdateSource** _(Write)_: Path to the source of updates to be applied during installation. +* **[String] SQMReporting** _(Write)_: Enable customer experience reporting. +* **[String] ErrorReporting** _(Write)_: Enable error reporting. +* **[String] InstallSharedDir** _(Write)_: Installation path for shared SQL files. +* **[String] InstallSharedWOWDir** _(Write)_: Installation path for x86 shared SQL files. +* **[String] InstanceDir** _(Write)_: Installation path for SQL instance files. +* **[PSCredential] SQLSvcAccount** _(Write)_: Service account for the SQL service. +* **[PSCredential] AgtSvcAccount** _(Write)_: Service account for the SQL Agent service. +* **[String] SQLCollation** _(Write)_: Collation for SQL. +* **[String[]] SQLSysAdminAccounts** _(Write)_: Array of accounts to be made SQL administrators. +* **[String] SecurityMode** _(Write)_: Security mode to apply to the SQL Server instance. +* **[PSCredential] SAPwd** _(Write)_: SA password, if SecurityMode is set to 'SQL'. +* **[String] InstallSQLDataDir** _(Write)_: Root path for SQL database files. +* **[String] SQLUserDBDir** _(Write)_: Path for SQL database files. +* **[String] SQLUserDBLogDir** _(Write)_: Path for SQL log files. +* **[String] SQLTempDBDir** _(Write)_: Path for SQL TempDB files. +* **[String] SQLTempDBLogDir** _(Write)_: Path for SQL TempDB log files. +* **[String] SQLBackupDir** _(Write)_: Path for SQL backup files. +* **[PSCredential] FTSvcAccount** _(Write)_: Service account for the Full Text service. +* **[PSCredential] RSSvcAccount** _(Write)_: Service account for Reporting Services service. +* **[PSCredential] ASSvcAccount** _(Write)_: Service account for Analysis Services service. +* **[String] ASCollation** _(Write)_: Collation for Analysis Services. +* **[String[]] ASSysAdminAccounts** _(Write)_: Array of accounts to be made Analysis Services admins. +* **[String] ASDataDir** _(Write)_: Path for Analysis Services data files. +* **[String] ASLogDir** _(Write)_: Path for Analysis Services log files. +* **[String] ASBackupDir** _(Write)_: Path for Analysis Services backup files. +* **[String] ASTempDir** _(Write)_: Path for Analysis Services temp files. +* **[String] ASConfigDir** _(Write)_: Path for Analysis Services config. +* **[PSCredential] ISSvcAccount** _(Write)_: Service account for Integration Services service. +* **[String] BrowserSvcStartupType** _(Write)_: Specifies the startup mode for SQL Server Browser service. { Automatic | Disabled | 'Manual' } +* **[String] FailoverClusterGroupName** _(Write)_: The name of the resource group to create for the clustered SQL Server instance. Defaults to 'SQL Server (_InstanceName_)'. +* **[String[]]FailoverClusterIPAddress** _(Write)_: Array of IP Addresses to be assigned to the clustered SQL Server instance. IP addresses must be in [dotted-decimal notation](https://en.wikipedia.org/wiki/Dot-decimal_notation), for example ````10.0.0.100````. If no IP address is specified, uses 'DEFAULT' for this setup parameter. +* **[String] FailoverClusterNetworkName** _(Write)_: Host name to be assigned to the clustered SQL Server instance. + +#### Read-Only Properties from Get-TargetResource + +* **SQLSvcAccountUsername** _(Read)_: Output user name for the SQL service. +* **AgtSvcAccountUsername** _(Read)_: Output user name for the SQL Agent service. +* **FTSvcAccountUsername** _(Read)_: Output username for the Full Text service. +* **RSSvcAccountUsername** _(Read)_: Output username for the Reporting Services service. +* **ASSvcAccountUsername** _(Read)_: Output username for the Analysis Services service. +* **ISSvcAccountUsername** _(Read)_: Output user name for the Integration Services service. + +#### Examples + +None. + +### xWaitforAvailabilityGroup + +No description. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2. + +#### Parameters + +* **[String] Name** _(Key)_: Name for availability group +* **[Uint64] RetryIntervalSec** _(Write)_: Interval to check for availability group +* **[Uint32] RetryCount** _(Write)_: Maximum number of retries to check availability group creation + +#### Read-Only Properties from Get-TargetResource + +None. + +#### Examples -* **Ensure**: Determines whether the alias should be added or removed. Default value is 'Present' -* **Name**: (Key) The name of Alias (e.g. svr01\inst01). -* **ServerName**: (Key) The SQL Server you are aliasing (the netbios name or FQDN). -* **Protocol**: Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes). Default value is 'TCP'. -* **TCPPort**: The TCP port SQL is listening on. Only used when protocol is set to 'TCP'. Default value is port 1433. -* **UseDynamicTcpPort**: The UseDynamicTcpPort specify that the Net-Library will determine the port dynamically. The port specified in Port number will not be used. Default value is '$false'. -* **PipeName**: (Read) Named Pipes path from the Get-TargetResource method. +None. diff --git a/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 b/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 index 8f483465b..816a493d2 100644 --- a/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLAOGroupEnsure.Tests.ps1 @@ -2,339 +2,327 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLAOGroupEnsure' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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\')) - } +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLAOGroupEnsure' - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +#region HEADER - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +# Unit Test Template Version: 1.1.0 +[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\')) +} - #endregion HEADER +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - try - { +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit - #region Pester Test Initialization +#endregion HEADER - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +try +{ - $mockpassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force - $mockusername = "dba" - $mockcredential = New-Object System.Management.Automation.PSCredential($mockusername,$mockpassword) + #region Pester Test Initialization - #endregion Pester Test Initialization - - #region Get-TargetResource - Describe 'Get-TargetResource' { - Mock -CommandName Connect-SQL -MockWith { - # build a custom object to return which is close to the real SMO object - $smoObj = [PSCustomObject] @{ - SQLServer = 'Node01' - SQLInstanceName = 'Prd01' - ClusterName = 'Clust01' - } + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later - $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ - 'AG01' = @{ - AvailabilityGroupListeners = @{ - name = 'AgList01' - availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) - portnumber = 5022 - } - - AvailabilityDatabases = @( - @{ - name='AdventureWorks' - } - ) + $mockpassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force + $mockusername = "dba" + $mockcredential = New-Object System.Management.Automation.PSCredential($mockusername,$mockpassword) + + #endregion Pester Test Initialization + + #region Get-TargetResource + Describe 'Get-TargetResource' { + Mock -CommandName Connect-SQL -MockWith { + # build a custom object to return which is close to the real SMO object + $smoObj = [PSCustomObject] @{ + SQLServer = 'Node01' + SQLInstanceName = 'Prd01' + ClusterName = 'Clust01' + } + + # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later + $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ + 'AG01' = @{ + AvailabilityGroupListeners = @{ + name = 'AgList01' + availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) + portnumber = 5022 } + + AvailabilityDatabases = @( + @{ + name='AdventureWorks' + } + ) } + } - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force - $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { - return $true - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force + $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { + return $true + } -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { - return 'AG01' - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { + return 'AG01' + } -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { - return $true - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { + return $true + } -Force - return $smoObj - } -ModuleName $script:DSCResourceName - - Context "When the system is in the desired state" { - $SqlAOGroup = Get-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + return $smoObj + } -ModuleName $script:DSCResourceName - It 'Should return hashtable with Ensure = $true'{ - $SqlAOGroup.Ensure | Should Be $true - } + Context "When the system is in the desired state" { + $SqlAOGroup = Get-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return hashtable with Ensure = $true'{ + $SqlAOGroup.Ensure | Should Be $true } - - Context "When the system is not in the desired state" { - $SqlAOGroup = Get-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential - - It 'Should return hashtable with Ensure = $false' { - $SqlAOGroup.Ensure | Should Be $false - } + } + + Context "When the system is not in the desired state" { + $SqlAOGroup = Get-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return hashtable with Ensure = $false' { + $SqlAOGroup.Ensure | Should Be $false } } - #endregion Get-TargetResource - - #region Test-TargetResource - Describe 'Test-TargetResource' { - Mock -CommandName Connect-SQL -MockWith { - # build a custom object to return which is close to the real SMO object - $smoObj = [PSCustomObject] @{ - SQLServer = 'Node01' - SQLInstanceName = 'Prd01' - ClusterName = 'Clust01' - } + } + #endregion Get-TargetResource + + #region Test-TargetResource + Describe 'Test-TargetResource' { + Mock -CommandName Connect-SQL -MockWith { + # build a custom object to return which is close to the real SMO object + $smoObj = [PSCustomObject] @{ + SQLServer = 'Node01' + SQLInstanceName = 'Prd01' + ClusterName = 'Clust01' + } - # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later - $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ - 'AG01' = @{ - AvailabilityGroupListeners = @{ - name = 'AgList01' - availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) - portnumber = 5022 - } - - AvailabilityDatabases = @( - @{ - name='AdventureWorks' - } - ) + # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later + $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ + 'AG01' = @{ + AvailabilityGroupListeners = @{ + name = 'AgList01' + availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) + portnumber = 5022 } + + AvailabilityDatabases = @( + @{ + name='AdventureWorks' + } + ) } + } - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force - $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { - return $true - } -Force - - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { - return 'AG01' - } -Force - - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { - return $true - } -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force + $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value { + return $true + } -Force + + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value { + return 'AG01' + } -Force + + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value { + return $true + } -Force + + return $smoObj + } -ModuleName $script:DSCResourceName + + Context "When the system is in the desired state" { + $SqlAOGroupTest = Test-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return $true'{ + $SqlAOGroupTest | Should Be $true + } + } + + Context "When the system is not in the desired state" { + $SqlAOGroupTest = Test-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential + + It 'Should return $false' { + $SqlAOGroupTest | Should Be $false + } + } + } + #endregion Test-TargetResource + + Describe 'Set-TargetResource' { + # Mocking the module FailoverCluster, to be able to mock the function Get-ClusterNode + Get-Module -Name FailoverClusters | Remove-Module + New-Module -Name FailoverClusters -ScriptBlock { + # This was generated by Write-ModuleStubFile function from the folder Tests\Unit\Stubs + function Get-ClusterNode { + [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216215')] + param( + [Parameter(Position=0)] + [ValidateNotNullOrEmpty()] + [System.Collections.Specialized.StringCollection] + ${Name}, + + [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] + [ValidateNotNull()] + [psobject] + ${InputObject}, + + [ValidateNotNullOrEmpty()] + [string] + ${Cluster} + ) - return $smoObj - } -ModuleName $script:DSCResourceName - - Context "When the system is in the desired state" { - $SqlAOGroupTest = Test-TargetResource -Ensure 'Present' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential - - It 'Should return $true'{ - $SqlAOGroupTest | Should Be $true - } + throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand } - Context "When the system is not in the desired state" { - $SqlAOGroupTest = Test-TargetResource -Ensure 'Absent' -AvailabilityGroupName 'AG01' -SQLServer 'localhost' -SQLInstanceName 'MSSQLSERVER' -SetupCredential $mockcredential - - It 'Should return $false' { - $SqlAOGroupTest | Should Be $false + Export-ModuleMember -Function *-Cluster* + } | Import-Module -Force + + Mock Grant-ServerPerms -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock New-ListenerADObject -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock Get-ClusterNode -MockWith { + $clusterNode = @( + [PSCustomObject] @{ + Name = 'Node01' + }, + [PSCustomObject] @{ + Name = 'Node02' + }, + [PSCustomObject] @{ + Name = 'Node03' + }, + [PSCustomObject] @{ + Name = 'Node04' } + ) + return $clusterNode + } -ModuleName $script:DSCResourceName -Verifiable + + Mock Connect-SQL -MockWith { + # build a custom object to return which is close to the real SMO object + $smoObj = [PSCustomObject] @{ + SQLServer = 'Node01' + SQLInstanceName = 'Prd01' + ClusterName = 'Clust01' } - } - #endregion Test-TargetResource - - Describe 'Set-TargetResource' { - # Mocking the module FailoverCluster, to be able to mock the function Get-ClusterNode - Get-Module -Name FailoverClusters | Remove-Module - New-Module -Name FailoverClusters -ScriptBlock { - # This was generated by Write-ModuleStubFile function from the folder Tests\Unit\Stubs - function Get-ClusterNode { - [CmdletBinding(DefaultParameterSetName='InputObject', HelpUri='http://go.microsoft.com/fwlink/?LinkId=216215')] - param( - [Parameter(Position=0)] - [ValidateNotNullOrEmpty()] - [System.Collections.Specialized.StringCollection] - ${Name}, - - [Parameter(ParameterSetName='InputObject', ValueFromPipeline=$true)] - [ValidateNotNull()] - [psobject] - ${InputObject}, - - [ValidateNotNullOrEmpty()] - [string] - ${Cluster} - ) - throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand - } - - Export-ModuleMember -Function *-Cluster* - } | Import-Module -Force - - Mock Grant-ServerPerms -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock New-ListenerADObject -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock Get-ClusterNode -MockWith { - $clusterNode = @( - [PSCustomObject] @{ - Name = 'Node01' - }, - [PSCustomObject] @{ - Name = 'Node02' - }, - [PSCustomObject] @{ - Name = 'Node03' - }, - [PSCustomObject] @{ - Name = 'Node04' + # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later + $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ + 'AG01' = @{ + AvailabilityGroupListeners = @{ + name = 'AgList01' + availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) + portnumber = 5022 } - ) - return $clusterNode - } -ModuleName $script:DSCResourceName -Verifiable - - Mock Connect-SQL -MockWith { - # build a custom object to return which is close to the real SMO object - $smoObj = [PSCustomObject] @{ - SQLServer = 'Node01' - SQLInstanceName = 'Prd01' - ClusterName = 'Clust01' - } - # add the AvailabilityGroups entry as this is an ArrayList and allows us the functionality later - $smoObj | Add-Member -MemberType NoteProperty -Name 'AvailabilityGroups' -Value @{ - 'AG01' = @{ - AvailabilityGroupListeners = @{ - name = 'AgList01' - availabilitygrouplisteneripaddresses = [System.Collections.ArrayList] @(@{IpAddress = '192.168.0.1'; SubnetMask = '255.255.255.0'}) - portnumber = 5022 + AvailabilityDatabases = @( + @{ + name='AdventureWorks' } + ) + } + } - AvailabilityDatabases = @( - @{ - name='AdventureWorks' + $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value {return $true} -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value {return 'AG01'} -Force + $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value {return $true} -Force + + return $smoObj + } -ModuleName $script:DSCResourceName -Verifiable + + Mock New-Object -MockWith { + Param($TypeName) + Switch ($TypeName) + { + 'Microsoft.SqlServer.Management.Smo.AvailabilityGroup' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + AutomatedBackupPreference = '' + FailureConditionLevel = '' + HealthCheckTimeout = '' + AvailabilityReplicas = [System.Collections.ArrayList] @() + AvailabilityGroupListeners = [System.Collections.ArrayList] @() } - ) - } + $object | Add-Member -MemberType ScriptMethod -Name Create -Value {return $true} } - $smoObj.AvailabilityGroups | Add-Member -MemberType ScriptMethod -Name 'Add' -Value {return $true} -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType NoteProperty -Name Name -Value 'AG01' -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name ToString -Value {return 'AG01'} -Force - $smoObj.AvailabilityGroups['AG01'] | Add-Member -MemberType ScriptMethod -Name Drop -Value {return $true} -Force - - return $smoObj - } -ModuleName $script:DSCResourceName -Verifiable - - Mock New-Object -MockWith { - Param($TypeName) - Switch ($TypeName) - { - 'Microsoft.SqlServer.Management.Smo.AvailabilityGroup' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - AutomatedBackupPreference = '' - FailureConditionLevel = '' - HealthCheckTimeout = '' - AvailabilityReplicas = [System.Collections.ArrayList] @() - AvailabilityGroupListeners = [System.Collections.ArrayList] @() - } - $object | Add-Member -MemberType ScriptMethod -Name Create -Value {return $true} - } - - 'Microsoft.SqlServer.Management.Smo.AvailabilityReplica' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - EndpointUrl = '' - FailoverMode = '' - AvailabilityMode = '' - BackupPriority = 0 - ConnectionModeInPrimaryRole = '' - ConnectionModeInSecondaryRole = '' - } - } - - 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - PortNumber = '' - AvailabilityGroupListenerIPAddresses = [System.Collections.ArrayList] @() - } - } - - 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress' { - $object = [PSCustomObject] @{ - Name = "MockedObject" - IsDHCP = '' - IPAddress = '' - SubnetMask = '' - } - } + 'Microsoft.SqlServer.Management.Smo.AvailabilityReplica' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + EndpointUrl = '' + FailoverMode = '' + AvailabilityMode = '' + BackupPriority = 0 + ConnectionModeInPrimaryRole = '' + ConnectionModeInSecondaryRole = '' + } + } - Default { - $object = [PSCustomObject] @{ - Name = "MockedObject" - } - } + 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + PortNumber = '' + AvailabilityGroupListenerIPAddresses = [System.Collections.ArrayList] @() + } } - return $object - } -ModuleName $script:DSCResourceName -Verifiable - - Context "When the system is not in the desired state" { - $params = @{ - Ensure = 'Present' - AvailabilityGroupName = 'AG01' - AvailabilityGroupNameListener = 'AgList01' - AvailabilityGroupNameIP = '192.168.0.1' - AvailabilityGroupSubMask = '255.255.255.0' - AvailabilityGroupPort = 1433 - ReadableSecondary = 'ReadOnly' - AutoBackupPreference = 'Primary' - SQLServer = 'localhost' - SQLInstanceName = 'MSSQLSERVER' - SetupCredential = $mockcredential + 'Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress' { + $object = [PSCustomObject] @{ + Name = "MockedObject" + IsDHCP = '' + IPAddress = '' + SubnetMask = '' + } } - It 'Should not throw when calling Set-method' { - { Set-TargetResource @Params } | Should Not Throw + Default { + $object = [PSCustomObject] @{ + Name = "MockedObject" + } } } - } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + return $object + } -ModuleName $script:DSCResourceName -Verifiable + + Context "When the system is not in the desired state" { + $params = @{ + Ensure = 'Present' + AvailabilityGroupName = 'AG01' + AvailabilityGroupNameListener = 'AgList01' + AvailabilityGroupNameIP = '192.168.0.1' + AvailabilityGroupSubMask = '255.255.255.0' + AvailabilityGroupPort = 1433 + ReadableSecondary = 'ReadOnly' + AutoBackupPreference = 'Primary' + SQLServer = 'localhost' + SQLInstanceName = 'MSSQLSERVER' + SetupCredential = $mockcredential + } - #endregion + It 'Should not throw when calling Set-method' { + { Set-TargetResource @Params } | Should Not Throw + } + } } } +finally +{ + #region FOOTER -$testJob | Receive-Job -Wait + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 index 361e43be7..ef96a7cf3 100644 --- a/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 @@ -1,633 +1,527 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerAvailabilityGroupListener' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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\')) +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerAvailabilityGroupListener' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + # Loading stub cmdlets + Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SQLPSStub.psm1') -Force + + # Static parameter values + $nodeName = 'localhost' + $instanceName = 'DEFAULT' + $availabilityGroup = 'AG01' + $listnerName = 'AGListner' + + $defaultParameters = @{ + InstanceName = $instanceName + NodeName = $nodeName + Name = $listnerName + AvailabilityGroup = $availabilityGroup } - 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 - - # Begin Testing - try - { - #region Pester Test Initialization - - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - - # Loading stub cmdlets - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SQLPSStub.psm1') -Force - - # Static parameter values - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $availabilityGroup = 'AG01' - $listnerName = 'AGListner' - - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Name = $listnerName - AvailabilityGroup = $availabilityGroup + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as absent' { + $result.Ensure | Should Be 'Absent' + } + + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup + } + + It 'Should not return any IP addresses' { + $result.IpAddress | Should Be $null + } + + It 'Should not return port' { + $result.Port | Should Be 0 + } + + It 'Should return that DHCP is not used' { + $result.DHCP | Should Be $false + } + + It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state, without DHCP' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } + + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup + } + + It 'Should return correct IP address' { + $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' + } + + It 'Should return correct port' { + $result.Port | Should Be 5030 + } + + It 'Should return that DHCP is not used' { + $result.DHCP | Should Be $false + } + + It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - #endregion Pester Test Initialization + Context 'When the system is in the desired state, with DHCP' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5031 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Context 'When the system is not in the desired state' { + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup + } + + It 'Should return correct IP address' { + $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' + } + + It 'Should return correct port' { + $result.Port | Should Be 5031 + } + + It 'Should return that DHCP is used' { + $result.DHCP | Should Be $true + } + + It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Context 'When the system is not in the desired state (for static IP)' { + It 'Should return that desired state is absent when wanted desired state is to be Present' { $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - It 'Should return the desired state as absent' { - $result.Ensure | Should Be 'Absent' - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should not return any IP addresses' { - $result.IpAddress | Should Be $null - } + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should not return port' { - $result.Port | Should Be 0 - } + It 'Should return that desired state is absent when wanted desired state is to be Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + } - It 'Should return that DHCP is not used' { - $result.DHCP | Should Be $false - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state, without DHCP' { + It 'Should return that desired state is absent when IP address is different' { $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup - } + It 'Should return that desired state is absent when DHCP is absent but should be present' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return correct IP address' { - $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return correct port' { - $result.Port | Should Be 5030 - } + It 'Should return that desired state is absent when DHCP is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + DHCP = $true + } - It 'Should return that DHCP is not used' { - $result.DHCP | Should Be $false - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state, with DHCP' { + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5555 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should return that desired state is absent when port is different' { $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $false + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5031 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) + Context 'When the system is not in the desired state (for DHCP)' { + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + It 'Should return that desired state is absent when DHCP is present but should be absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.100/255.255.255.0' + Port = 5030 + DHCP = $false + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.AvailabilityGroup | Should Be $testParameters.AvailabilityGroup - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return correct IP address' { - $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return correct port' { - $result.Port | Should Be 5031 - } + It 'Should return that desired state is absent when IP address is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.10.45/255.255.252.0' + } - It 'Should return that DHCP is used' { - $result.DHCP | Should Be $true - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5555 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should return that desired state is absent when port is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + Port = 5030 + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state (for static IP)' { - It 'Should return that desired state is absent when wanted desired state is to be Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when wanted desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when IP address is different' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when DHCP is absent but should be present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $true - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when DHCP is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - DHCP = $true - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5555 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when port is different' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - Context 'When the system is not in the desired state (for DHCP)' { - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when DHCP is present but should be absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.100/255.255.255.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is absent when IP address is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.10.45/255.255.252.0' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5555 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is absent when port is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - Port = 5030 - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - Context 'When the system is in the desired state (for static IP)' { - It 'Should return that desired state is present when wanted desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state is present when wanted desired state is to be Present, without DHCP' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $false - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is present when IP address is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.0.1/255.255.255.0' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should return that desired state is present when port is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - Port = 5030 - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - Context 'When the system is in the desired state (for DHCP)' { - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $true -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) + Context 'When the system is in the desired state (for static IP)' { + It 'Should return that desired state is present when wanted desired state is to be Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return that desired state is present when wanted desired state is to be Present, with DHCP' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $true - } + It 'Should return that desired state is present when wanted desired state is to be Present, without DHCP' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $false + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return that desired state is present when DHCP is the only set parameter' { - $testParameters = $defaultParameters - $testParameters += @{ - DHCP = $true - } + It 'Should return that desired state is present when IP address is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks + It 'Should return that desired state is present when port is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + Port = 5030 + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName New-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Set-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Add-SqlAvailabilityGroupListenerStaticIp -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should call the cmdlet New-SqlAvailabilityGroupListener when system is not in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5030 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should throw when trying to change an existing IP address' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '10.0.0.1/255.255.252.0' - Port = 5030 - DHCP = $false - } - - { Set-TargetResource @testParameters } | Should Throw - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should throw when trying to change from static IP to DHCP' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $true - } - - { Set-TargetResource @testParameters } | Should Throw - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should call the cmdlet Add-SqlAvailabilityGroupListenerStaticIp, when adding another IP address, and system is not in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = @('192.168.0.1/255.255.255.0','10.0.0.1/255.255.252.0') - Port = 5030 - DHCP = $false - } - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener - return New-Object Object | - Add-Member NoteProperty PortNumber 5555 -PassThru | - Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { - return @( - # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection - (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress - Add-Member NoteProperty IsDHCP $false -PassThru | - Add-Member NoteProperty IPAddress '192.168.10.45' -PassThru | - Add-Member NoteProperty SubnetMask '255.255.252.0' -PassThru - ) + Context 'When the system is in the desired state (for DHCP)' { + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $true -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should return that desired state is present when wanted desired state is to be Present, with DHCP' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return that desired state is present when DHCP is the only set parameter' { + $testParameters = $defaultParameters + $testParameters += @{ + DHCP = $true + } - It 'Should call the cmdlet Set-SqlAvailabilityGroupListener when port is not in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.10.45/255.255.252.0' - Port = 5030 - DHCP = $false - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - Set-TargetResource @testParameters | Out-Null + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName New-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Set-SqlAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Add-SqlAvailabilityGroupListenerStaticIp -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should call the cmdlet New-SqlAvailabilityGroupListener when system is not in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters | Out-Null - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It } Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { @@ -646,51 +540,145 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable - Context 'When the system is in the desired state' { - It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - DHCP = $false - } - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - - It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state (without ensure parameter)' { - $testParameters = $defaultParameters - $testParameters += @{ - IpAddress = '192.168.0.1/255.255.255.0' - Port = 5030 - } - - Set-TargetResource @testParameters | Out-Null - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } - } - - Assert-VerifiableMocks + It 'Should throw when trying to change an existing IP address' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '10.0.0.1/255.255.252.0' + Port = 5030 + DHCP = $false + } + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should throw when trying to change from static IP to DHCP' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $true + } + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should call the cmdlet Add-SqlAvailabilityGroupListenerStaticIp, when adding another IP address, and system is not in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = @('192.168.0.1/255.255.255.0','10.0.0.1/255.255.252.0') + Port = 5030 + DHCP = $false + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5555 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.10.45' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.252.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should call the cmdlet Set-SqlAvailabilityGroupListener when port is not in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListener + return New-Object Object | + Add-Member NoteProperty PortNumber 5030 -PassThru | + Add-Member ScriptProperty AvailabilityGroupListenerIPAddresses { + return @( + # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection + (New-Object Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress + Add-Member NoteProperty IsDHCP $false -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is in the desired state' { + It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $false + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } - #endregion + It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state (without ensure parameter)' { + $testParameters = $defaultParameters + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + } + + Set-TargetResource @testParameters | Out-Null + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks } } +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment -$testJob | Receive-Job -Wait + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 index 1ab06da4d..557538aba 100644 --- a/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerConfiguration.Tests.ps1 @@ -1,191 +1,114 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerConfiguration' - - # Unit Test Template Version: 1.1.0 - [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\')) - } +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerConfiguration' + +# Unit Test Template Version: 1.1.0 +[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit - - $defaultState = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'user connections' - OptionValue = 0 - RestartService = $false - RestartTimeout = 120 - } +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +$defaultState = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'user connections' + OptionValue = 0 + RestartService = $false + RestartTimeout = 120 +} - $desiredState = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'user connections' - OptionValue = 500 - RestartService = $false - RestartTimeout = 120 - } +$desiredState = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'user connections' + OptionValue = 500 + RestartService = $false + RestartTimeout = 120 +} - $desiredStateRestart = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'user connections' - OptionValue = 5000 - RestartService = $true - RestartTimeout = 120 - } +$desiredStateRestart = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'user connections' + OptionValue = 5000 + RestartService = $true + RestartTimeout = 120 +} - $dynamicOption = @{ - SQLServer = 'CLU02' - SQLInstanceName = 'ClusteredInstance' - OptionName = 'show advanced options' - OptionValue = 0 - RestartService = $false - RestartTimeout = 120 - } +$dynamicOption = @{ + SQLServer = 'CLU02' + SQLInstanceName = 'ClusteredInstance' + OptionName = 'show advanced options' + OptionValue = 0 + RestartService = $false + RestartTimeout = 120 +} - $invalidOption = @{ - SQLServer = 'CLU01' - SQLInstanceName = 'MSSQLSERVER' - OptionName = 'Does Not Exist' - OptionValue = 1 - RestartService = $false - RestartTimeout = 120 - } +$invalidOption = @{ + SQLServer = 'CLU01' + SQLInstanceName = 'MSSQLSERVER' + OptionName = 'Does Not Exist' + OptionValue = 1 + RestartService = $false + RestartTimeout = 120 +} - ## compile the SMO stub - #Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +## compile the SMO stub +#Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - try - { - Describe "$($script:DSCResourceName)\Get-TargetResource" { +try +{ + Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName - Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName - Context 'The system is not in the desired state' { + Context 'The system is not in the desired state' { - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'user connections' - ConfigValue = 0 - } - ) - } + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'user connections' + ConfigValue = 0 + } + ) } - - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - - return $mock - } -ModuleName $script:DSCResourceName -Verifiable - - ## Get the current state - $result = Get-TargetResource @desiredState - - It 'Should return the same values as passed' { - $result.SQLServer | Should Be $desiredState.SQLServer - $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName - $result.OptionName | Should Be $desiredState.OptionName - $result.OptionValue | Should Not Be $desiredState.OptionValue - $result.RestartService | Should Be $desiredState.RestartService - $result.RestartTimeout | Should Be $desiredState.RestartTimeout } - It 'Should call Connect-SQL mock when getting the current state' { - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 - } - } - - Context 'The system is in the desired state' { - - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'user connections' - ConfigValue = 500 - } - ) - } - } - - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - - return $mock - } -ModuleName $script:DSCResourceName -Verifiable - - ## Get the current state - $result = Get-TargetResource @desiredState + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - It 'Should return the same values as passed' { - $result.SQLServer | Should Be $desiredState.SQLServer - $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName - $result.OptionName | Should Be $desiredState.OptionName - $result.OptionValue | Should Be $desiredState.OptionValue - $result.RestartService | Should Be $desiredState.RestartService - $result.RestartTimeout | Should Be $desiredState.RestartTimeout - } + return $mock + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should call Connect-SQL mock when getting the current state' { - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 - } + ## Get the current state + $result = Get-TargetResource @desiredState + + It 'Should return the same values as passed' { + $result.SQLServer | Should Be $desiredState.SQLServer + $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName + $result.OptionName | Should Be $desiredState.OptionName + $result.OptionValue | Should Not Be $desiredState.OptionValue + $result.RestartService | Should Be $desiredState.RestartService + $result.RestartTimeout | Should Be $desiredState.RestartTimeout } - Context 'Invalid data is supplied' { - - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'user connections' - ConfigValue = 0 - } - ) - } - } - - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - - return $mock - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should call New-TerminatingError mock when a bad option name is specified' { - { Get-TargetResource @invalidOption } | Should Throw 'ConfigurationOptionNotFound' - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-TerminatingError -Scope It -Times 1 - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 - } + It 'Should call Connect-SQL mock when getting the current state' { + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - - Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName + Context 'The system is in the desired state' { Mock -CommandName Connect-SQL -MockWith { $mock = New-Object PSObject -Property @{ @@ -205,19 +128,24 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { return $mock } -ModuleName $script:DSCResourceName -Verifiable - It 'Should cause Test-TargetResource to return false when not in the desired state' { - Test-TargetResource @defaultState | Should be $false + ## Get the current state + $result = Get-TargetResource @desiredState + + It 'Should return the same values as passed' { + $result.SQLServer | Should Be $desiredState.SQLServer + $result.SQLInstanceName | Should Be $desiredState.SQLInstanceName + $result.OptionName | Should Be $desiredState.OptionName + $result.OptionValue | Should Be $desiredState.OptionValue + $result.RestartService | Should Be $desiredState.RestartService + $result.RestartTimeout | Should Be $desiredState.RestartTimeout } - It 'Should cause Test-TargetResource method to return true' { - Test-TargetResource @desiredState | Should be $true + It 'Should call Connect-SQL mock when getting the current state' { + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 } } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName - - Mock -CommandName New-TerminatingError -MockWith {} -ModuleName $script:DSCResourceName + Context 'Invalid data is supplied' { Mock -CommandName Connect-SQL -MockWith { $mock = New-Object PSObject -Property @{ @@ -226,7 +154,6 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { @{ DisplayName = 'user connections' ConfigValue = 0 - IsDynamic = $false } ) } @@ -236,59 +163,119 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} return $mock - } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU01' } + } -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Connect-SQL -MockWith { - $mock = New-Object PSObject -Property @{ - Configuration = @{ - Properties = @( - @{ - DisplayName = 'show advanced options' - ConfigValue = 1 - IsDynamic = $true - } - ) - } + It 'Should call New-TerminatingError mock when a bad option name is specified' { + { Get-TargetResource @invalidOption } | Should Throw 'ConfigurationOptionNotFound' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-TerminatingError -Scope It -Times 1 + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 1 + } + } + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + + Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName + + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'user connections' + ConfigValue = 500 + } + ) } + } - ## add the Alter method - $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} - return $mock - } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU02' } + return $mock + } -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Restart-SqlService -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + It 'Should cause Test-TargetResource to return false when not in the desired state' { + Test-TargetResource @defaultState | Should be $false + } - Mock -CommandName New-WarningMessage -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + It 'Should cause Test-TargetResource method to return true' { + Test-TargetResource @desiredState | Should be $true + } + } - Context 'Change the system to the desired state' { - It 'Should not restart SQL for a dynamic option' { - Set-TargetResource @dynamicOption - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly - } + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName - It 'Should restart SQL for a non-dynamic option' { - Set-TargetResource @desiredStateRestart - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 1 -Exactly - } - - It 'Should warn about restart when required, but not requested' { - Set-TargetResource @desiredState + Mock -CommandName New-TerminatingError -MockWith {} -ModuleName $script:DSCResourceName - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-WarningMessage -Scope It -Times 1 -Exactly - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'user connections' + ConfigValue = 0 + IsDynamic = $false + } + ) } + } - It 'Should call Connect-SQL to get option values' { - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 3 + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} + + return $mock + } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU01' } + + Mock -CommandName Connect-SQL -MockWith { + $mock = New-Object PSObject -Property @{ + Configuration = @{ + Properties = @( + @{ + DisplayName = 'show advanced options' + ConfigValue = 1 + IsDynamic = $true + } + ) } } + + ## add the Alter method + $mock.Configuration | Add-Member -MemberType ScriptMethod -Name Alter -Value {} + + return $mock + } -ModuleName $script:DSCResourceName -Verifiable -ParameterFilter { $SQLServer -eq 'CLU02' } + + Mock -CommandName Restart-SqlService -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName New-WarningMessage -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + + Context 'Change the system to the desired state' { + It 'Should not restart SQL for a dynamic option' { + Set-TargetResource @dynamicOption + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly + } + + It 'Should restart SQL for a non-dynamic option' { + Set-TargetResource @desiredStateRestart + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 1 -Exactly + } + + It 'Should warn about restart when required, but not requested' { + Set-TargetResource @desiredState + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-WarningMessage -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Restart-SqlService -Scope It -Times 0 -Exactly + } + + It 'Should call Connect-SQL to get option values' { + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope Context -Times 3 + } } } - finally - { - Restore-TestEnvironment -TestEnvironment $TestEnvironment - } } - -$testJob | Receive-Job -Wait +finally +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} diff --git a/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 index 3444cd967..a0bcab475 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabase.Tests.ps1 @@ -1,229 +1,216 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerDatabase' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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\')) - } +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabase' - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +#region HEADER - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +# Unit Test Template Version: 1.1.0 +[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\')) +} - #endregion HEADER +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - # Begin Testing - try - { - #region Pester Test Initialization +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +#endregion HEADER - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' +# Begin Testing +try +{ + #region Pester Test Initialization - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName - } + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - #endregion Pester Test Initialization + $nodeName = 'localhost' + $instanceName = 'MSSQLSERVER' - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + $defaultParameters = @{ + SQLInstanceName = $instanceName + SQLServer = $nodeName + } - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownDatabase' - } + #endregion Pester Test Initialization - $result = Get-TargetResource @testParameters + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the state as absent' { - $result.Ensure | Should Be 'Absent' - } + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + $result = Get-TargetResource @testParameters - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the state as absent' { + $result.Ensure | Should Be 'Absent' } - - Context 'When the system is in the desired state for a database' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - } - - $result = Get-TargetResource @testParameters - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for a database' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + } + + $result = Get-TargetResource @testParameters - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name } - Assert-VerifiableMocks + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should return the state as absent when desired database does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownDatabase' - } - $result = Test-TargetResource @testParameters - $result | Should Be $false + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is not in the desired state' { + It 'Should return the state as absent when desired database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' } - It 'Should return the state as present when desired database exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - $result = Test-TargetResource @testParameters - $result | Should Be $true + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should return the state as present when desired database exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' } - } - Context 'When the system is in the desired state' { - It 'Should return the state as present when desired database exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - $result = Test-TargetResource @testParameters - $result | Should Be $true + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is in the desired state' { + It 'Should return the state as present when desired database exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' } - - It 'Should return the state as absent when desired database does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownDatabase' - } - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + + It 'Should return the state as absent when desired database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + } - Assert-VerifiableMocks + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Mock -CommandName New-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Mock -CommandName Remove-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should call the function New-SqlDatabase when desired database should be present' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'NewDatabase' - Ensure = 'Present' + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - Set-TargetResource @testParameters + Mock -CommandName New-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Remove-SqlDatabase -MockWith {} -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled New-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is not in the desired state' { + It 'Should call the function New-SqlDatabase when desired database should be present' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'NewDatabase' + Ensure = 'Present' } + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled New-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + } - Context 'When the system is not in the desired state' { - It 'Should call the function Remove-SqlDatabase when desired database should be absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'AdventureWorks' - Ensure = 'Absent' - } + Context 'When the system is not in the desired state' { + It 'Should call the function Remove-SqlDatabase when desired database should be absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + Ensure = 'Absent' + } - Set-TargetResource @testParameters + Set-TargetResource @testParameters - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabase -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } } } - finally - { - #region FOOTER +} +finally +{ + #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - } + #endregion } - -$testJob | Receive-Job -Wait diff --git a/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 index c2869ee80..a4e1bc2ed 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabaseOwner.Tests.ps1 @@ -1,270 +1,261 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerDatabaseOwner' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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\')) +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabaseOwner' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + $defaultParameters = @{ + SQLInstanceName = 'MSSQLSERVER' + SQLServer = 'localhost' } - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } + + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable - $TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit - #endregion HEADER + $result = Get-TargetResource @testParameters - # Begin Testing - try - { - #region Pester Test Initialization + It 'Should return the name as null from the get method' { + $result.Name | Should Be $null + } - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database + } - $defaultParameters = @{ - SQLInstanceName = 'MSSQLSERVER' - SQLServer = 'localhost' + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - #endregion Pester Test Initialization + Context 'When the specified database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'UnknownDatabase' + Name = 'CONTOSO\SqlServiceAcct' + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + return $null } -ModuleName $script:DSCResourceName -Verifiable - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + $result = Get-TargetResource @testParameters - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - return $null - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should return the name as null from the get method' { + $result.Name | Should Be $null + } - $result = Get-TargetResource @testParameters + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database + } - It 'Should return the name as null from the get method' { - $result.Name | Should Be $null - } + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Database | Should Be $testParameters.Database - } + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Mock -CommandName Get-SqlDatabaseOwner -MockWith { return 'CONTOSO\SqlServiceAcct' } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the name of the owner from the get method' { + $result.Name | Should Be $testParameters.Name } - Context 'When the specified database does not exist' { + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should return the state as false when desired login is not the database owner' { $testParameters = $defaultParameters $testParameters += @{ - Database = 'UnknownDatabase' + Database = 'AdventureWorks' Name = 'CONTOSO\SqlServiceAcct' } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { + Mock -CommandName Get-SqlDatabaseOwner -MockWith { return $null } -ModuleName $script:DSCResourceName -Verifiable - $result = Get-TargetResource @testParameters - - It 'Should return the name as null from the get method' { - $result.Name | Should Be $null - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Database | Should Be $testParameters.Database - } - - It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + } - Context 'When the system is in the desired state' { + Context 'When the system is in the desired state' { + It 'Should return the state as true when desired login is the database owner' { $testParameters = $defaultParameters $testParameters += @{ Database = 'AdventureWorks' Name = 'CONTOSO\SqlServiceAcct' } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { return 'CONTOSO\SqlServiceAcct' } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + 'CONTOSO\SqlServiceAcct' + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should return the name of the owner from the get method' { - $result.Name | Should Be $testParameters.Name - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Database | Should Be $testParameters.Database - } - - It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseOwner' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + Assert-VerifiableMocks + } - Context 'When the system is not in the desired state' { - It 'Should return the state as false when desired login is not the database owner' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - return $null - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should call the function Set-SqlDatabaseOwner when desired login is not the database owner' { + Mock -CommandName Set-SqlDatabaseOwner -MockWith { } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $false + Set-TargetResource @testParameters - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { - It 'Should return the state as true when desired login is the database owner' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + $testParameters.Database = 'UnknownDatabase' - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - 'CONTOSO\SqlServiceAcct' - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should throw an error when desired database does not exist' { + Mock -CommandName Set-SqlDatabaseOwner -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $true + { Set-TargetResource @testParameters } | Should Throw "Failed to setting the owner of database UnknownDatabase" - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Databases { - return @{ - 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + It 'Should not call the function Set-SqlDatabaseOwner when desired login is the database owner' { + Mock -CommandName Get-SqlDatabaseOwner -MockWith { + 'CONTOSO\SqlServiceAcct' + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should call the function Set-SqlDatabaseOwner when desired login is not the database owner' { - Mock -CommandName Set-SqlDatabaseOwner -MockWith { } -ModuleName $script:DSCResourceName -Verifiable - - Set-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - - $testParameters.Database = 'UnknownDatabase' - - It 'Should throw an error when desired database does not exist' { - Mock -CommandName Set-SqlDatabaseOwner -MockWith { - return Throw - } -ModuleName $script:DSCResourceName -Verifiable - - { Set-TargetResource @testParameters } | Should Throw "Failed to setting the owner of database UnknownDatabase" - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } + Mock -CommandName Set-SqlDatabaseOwner -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Database = 'AdventureWorks' - Name = 'CONTOSO\SqlServiceAcct' - } + $result = Get-TargetResource @testParameters - It 'Should not call the function Set-SqlDatabaseOwner when desired login is the database owner' { - Mock -CommandName Get-SqlDatabaseOwner -MockWith { - 'CONTOSO\SqlServiceAcct' - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabaseOwner -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseOwner -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - } - finally - { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Assert-VerifiableMocks } } +finally +{ + #region FOOTER -$testJob | Receive-Job -Wait + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 new file mode 100644 index 000000000..faa2ddb2c --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 @@ -0,0 +1,359 @@ +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabasePermission' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[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 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + $defaultParameters = @{ + SQLInstanceName = 'MSSQLSERVER' + SQLServer = 'localhost' + Database = 'AdventureWorks' + Name = 'CONTOSO\SqlServiceAcct' + } + + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the state as absent' { + $result.Ensure | Should Be 'Absent' + $result.Permissions | Should Be $null + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + $result.PermissionState | Should Be $testParameters.PermissionState + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabasePermission' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for PermissionState equal to Grant' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { return @( 'Connect','Update' ) } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + $result.Permissions | Should Be $testParameters.Permissions + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + $result.PermissionState | Should Be $testParameters.PermissionState + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabasePermission' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for PermissionState equal to Deny' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Deny' + Permissions = @( 'Connect','Update' ) + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { return @( 'Connect','Update' ) } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + $result.Permissions | Should Be $testParameters.Permissions + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + $result.PermissionState | Should Be $testParameters.PermissionState + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabasePermission' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + + It 'Should return the state as false when desired permissions does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + return $null + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state' { + It 'Should return the state as true when desired permissions exist for PermissionState equal to Grant' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + @( 'Connect','Update') + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return the state as true when desired permissions exist for PermissionState equal to Deny' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Deny' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + Mock -CommandName Get-SqlDatabasePermission -MockWith { + @( 'Connect','Update') + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Ensure = 'Present' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw an error when desired database does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should throw an error when desired login does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Granting - Should call the function Add-SqlDatabasePermission when desired state is already present' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.Ensure = 'Absent' + + It 'Granting - Should call the function Remove-SqlDatabasePermission when desired state is already absent' { + Mock -CommandName Remove-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.PermissionState = 'Deny' + + It 'Denying - Should call the function Remove-SqlDatabasePermission when desired state is already absent' { + Mock -CommandName Remove-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.Ensure = 'Present' + + It 'Denying - Should call the function Remove-SqlDatabasePermission when desired state is already present' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + PermissionState = 'Grant' + Ensure = 'Present' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw an error when desired database does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should throw an error when desired login does not exist' { + Mock -CommandName Add-SqlDatabasePermission -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should not call the function Add-SqlDatabasePermission when desired state is already present' { + Mock -CommandName Get-SqlDatabasePermission -MockWith { return @( 'Connect','Update' ) } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlDatabasePermission -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + + $testParameters.Ensure = 'Absent' + + It 'Should not call the function Remove-SqlDatabasePermission when desired state is already absent' { + Mock -CommandName Get-SqlDatabasePermission -MockWith { return $null } -ModuleName $script:DSCResourceName -Verifiable + Mock -CommandName Remove-SqlDatabasePermission -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Get-SqlDatabasePermission -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlDatabasePermission -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } +} +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 new file mode 100644 index 000000000..91abe6df2 --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 @@ -0,0 +1,236 @@ +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabaseRecoveryModel' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + $nodeName = 'localhost' + $instanceName = 'MSSQLSERVER' + + $defaultParameters = @{ + SQLInstanceName = $instanceName + SQLServer = $nodeName + } + + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Get-SqlDatabaseRecoveryModel -MockWith { + return 'Simple' + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the database does not exist' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + RecoveryModel = 'Full' + } + + $result = Get-TargetResource @testParameters + + It 'Should return null for RecoveryModel' { + $result.RecoveryModel | Should Be $null + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + It 'Should not call the mock function Get-SqlDatabaseRecoveryModel' { + Assert-MockCalled Get-SqlDatabaseRecoveryModel -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' + } + + $result = Get-TargetResource @testParameters + + It 'Should return wrong RecoveryModel' { + $result.RecoveryModel | Should Not Be $testParameters.RecoveryModel + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseRecoveryModel' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseRecoveryModel -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state for a database' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + $result = Get-TargetResource @testParameters + + It 'Should return the correct RecoveryModel' { + $result.RecoveryModel | Should Be $testParameters.RecoveryModel + } + + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock functions Connect-SQL and Get-SqlDatabaseRecoveryModel' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Get-SqlDatabaseRecoveryModel -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Get-SqlDatabaseRecoveryModel -MockWith { + return 'Simple' + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should return the state as false when desired recovery model is not correct' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Context 'When the system is in the desired state' { + It 'Should return the state as true when desired recovery model is correct' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Databases { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When desired database does not exist' { + It 'Should not call the mock function Set-SqlDatabaseRecoveryModel' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + RecoveryModel = 'Simple' + } + + Mock -CommandName Set-SqlDatabaseRecoveryModel -MockWith { + return Throw + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseRecoveryModel -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + } + } + + Context 'When the desired recovery model is not set' { + It 'Should call the function Set-SqlDatabaseRecoveryModel when desired recovery model should be present' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + Mock -CommandName Set-SqlDatabaseRecoveryModel -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlDatabaseRecoveryModel -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } + } +} +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 index 81570f34c..f98ecf322 100644 --- a/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 @@ -1,62 +1,141 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerEndpointPermission' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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\')) +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerEndpointPermission' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + $nodeName = 'localhost' + $instanceName = 'DEFAULT' + $principal = 'COMPANY\SqlServiceAcct' + $otherPrincipal = 'COMPANY\OtherAcct' + $endpointName = 'DefaultEndpointMirror' + + $defaultParameters = @{ + InstanceName = $instanceName + NodeName = $nodeName + Name = $endpointName + Principal = $principal } - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + #endregion Pester Test Initialization + + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as absent' { + $result.Ensure | Should Be 'Absent' + } - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.Principal | Should Be $testParameters.Principal + } - #endregion HEADER + It 'Should not return any permissions' { + $result.Permission | Should Be '' + } - # Begin Testing - try - { - #region Pester Test Initialization + It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Context 'When the system is in the desired state' { + $testParameters = $defaultParameters + + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru + ) + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + $result.Principal | Should Be $testParameters.Principal + } - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $principal = 'COMPANY\SqlServiceAcct' - $otherPrincipal = 'COMPANY\OtherAcct' - $endpointName = 'DefaultEndpointMirror' + It 'Should return the permissions passed as parameter' { + $result.Permission | Should Be 'CONNECT' + } - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Name = $endpointName - Principal = $principal + It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } - #endregion Pester Test Initialization + Assert-VerifiableMocks + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' + } + It 'Should return that desired state is absent when wanted desired state is to be Present' { Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint return New-Object Object | @@ -71,32 +150,20 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { ) } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - - It 'Should return the desired state as absent' { - $result.Ensure | Should Be 'Absent' - } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.Principal | Should Be $testParameters.Principal - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should not return any permissions' { - $result.Permission | Should Be '' - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' } - - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters + It 'Should return that desired state is absent when wanted desired state is to be Absent' { Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint return New-Object Object | @@ -111,331 +178,251 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { ) } -PassThru -Force } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - $result.Principal | Should Be $testParameters.Principal - } - - It 'Should return the permissions passed as parameter' { - $result.Permission | Should Be 'CONNECT' - } - - It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state' { + Context 'When the system is in the desired state' { + It 'Should return that desired state is present when wanted desired state is to be Present' { $testParameters = $defaultParameters $testParameters += @{ Ensure = 'Present' Permission = 'CONNECT' } - It 'Should return that desired state is absent when wanted desired state is to be Present' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + It 'Should return that desired state is present when wanted desired state is to be Absent' { $testParameters = $defaultParameters $testParameters += @{ Ensure = 'Absent' Permission = 'CONNECT' } - It 'Should return that desired state is absent when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $true - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + } - Context 'When the system is in the desired state' { - It 'Should return that desired state is present when wanted desired state is to be Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $true + Assert-VerifiableMocks + } - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Context 'When the system is not in the desired state' { + It 'Should call the the method Grant when desired state is to be Present' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' } - It 'Should return that desired state is present when wanted desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + return + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + throw 'Called Revoke() when it shouldn''t been called' + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - $result = Test-TargetResource @testParameters - $result | Should Be $true + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks - } + It 'Should call the the method Revoke when desired state is to be Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' + } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Context 'When the system is not in the desired state' { - It 'Should call the the method Grant when desired state is to be Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - return - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - throw 'Called Revoke() when it shouldn''t been called' - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + throw 'Called Grant() when it shouldn''t been called' + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + return + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw - { Set-TargetResource @testParameters } | Should Not Throw + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + } + } - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + Context 'When the system is in the desired state' { + It 'Should not throw error when desired state is already Present' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' } - It 'Should call the the method Revoke when desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - throw 'Called Grant() when it shouldn''t been called' - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $principal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - return - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + throw 'Called Grant() when it shouldn''t been called' + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + throw 'Called Revoke() when it shouldn''t been called' + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { - It 'Should not throw error when desired state is already Present' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $principal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - throw 'Called Grant() when it shouldn''t been called' - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal - ) - throw 'Called Revoke() when it shouldn''t been called' - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - { Set-TargetResource @testParameters } | Should Not Throw - - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should not throw error when desired state is already Absent' { + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' } - It 'Should not throw error when desired state is already Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty Name $endpointName -PassThru | - Add-Member ScriptMethod EnumObjectPermissions { - param($permissionSet) - return @( - (New-Object Object | - Add-Member NoteProperty Grantee $otherPrincipal -PassThru | - Add-Member NoteProperty PermissionState 'Grant' -PassThru - ) - ) - } -PassThru | - Add-Member ScriptMethod Grant { - param( - $permissionSet, - $principal - ) - throw 'Called Grant() when it shouldn''t been called' - } -PassThru | - Add-Member ScriptMethod Revoke { - param( - $permissionSet, - $principal + Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member NoteProperty Name $endpointName -PassThru | + Add-Member ScriptMethod EnumObjectPermissions { + param($permissionSet) + return @( + (New-Object Object | + Add-Member NoteProperty Grantee $otherPrincipal -PassThru | + Add-Member NoteProperty PermissionState 'Grant' -PassThru ) - throw 'Called Revoke() when it shouldn''t been called' - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + ) + } -PassThru | + Add-Member ScriptMethod Grant { + param( + $permissionSet, + $principal + ) + throw 'Called Grant() when it shouldn''t been called' + } -PassThru | + Add-Member ScriptMethod Revoke { + param( + $permissionSet, + $principal + ) + throw 'Called Revoke() when it shouldn''t been called' + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Assert-VerifiableMocks } + Assert-VerifiableMocks } - finally - { - #region FOOTER +} +finally +{ + #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - } + #endregion } - -$testJob | Receive-Job -Wait diff --git a/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 new file mode 100644 index 000000000..338311c62 --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 @@ -0,0 +1,1183 @@ +# Suppressing this rule because PlainText is required for one of the functions used in this test +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerFirewall' + +#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 { + <# + Testing two major versions to verify Integration Services differences (i.e service name). + No point in testing each supported SQL Server version, since there are no difference + between the other major versions. + #> + $testProductVersion = @( + 11, # SQL Server 2012 + 10 # SQL Server 2008 and 2008 R2 + ) + + $mockSqlDatabaseEngineName = 'MSSQL' + $mockSqlAgentName = 'SQLAgent' + $mockSqlFullTextName = 'MSSQLFDLauncher' + $mockSqlReportingName = 'ReportServer' + $mockSqlIntegrationName = 'MsDtsServer{0}0' # {0} will be replaced by SQL major version in runtime + $mockSqlAnalysisName = 'MSOLAP' + + $mockSqlDatabaseEngineInstanceIdName = $mockSqlDatabaseEngineName + $mockSqlAnalysisServicesInstanceIdName = 'MSAS' + + $mockSetupExecutableName = 'setup.exe' + $mockDatabaseEngineExecutableName = 'sqlservr.exe' + $mockIntegrationServicesExecutableName = 'MsDtsSrvr.exe' + + $mockFirewallRulePort_ReportingServicesNoSslProtocol = 'tcp' + $mockFirewallRulePort_ReportingServicesNoSslLocalPort = 80 + $mockFirewallRulePort_ReportingServicesSslProtocol = 'tcp' + $mockFirewallRulePort_ReportingServicesSslLocalPort = 443 + $mockFirewallRulePort_IntegrationServicesProtocol = 'tcp' + $mockFirewallRulePort_IntegrationServicesLocalPort = 135 + + $mockDefaultInstance_InstanceName = 'MSSQLSERVER' + + $mockSQLBrowserServiceName = 'SQLBrowser' + + $mockDefaultInstance_InstanceName = 'MSSQLSERVER' + $mockDefaultInstance_DatabaseServiceName = $mockDefaultInstance_InstanceName + $mockDefaultInstance_AgentServiceName = 'SQLSERVERAGENT' + $mockDefaultInstance_FullTextServiceName = $mockSqlFullTextName + $mockDefaultInstance_ReportingServiceName = $mockSqlReportingName + $mockDefaultInstance_IntegrationServiceName = $mockSqlIntegrationName + $mockDefaultInstance_AnalysisServiceName = 'MSSQLServerOLAPService' + + $mockNamedInstance_InstanceName = 'TEST' + $mockNamedInstance_DatabaseServiceName = "$($mockSqlDatabaseEngineName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_AgentServiceName = "$($mockSqlAgentName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_FullTextServiceName = "$($mockSqlFullTextName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_ReportingServiceName = "$($mockSqlReportingName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_IntegrationServiceName = $mockSqlIntegrationName + $mockNamedInstance_AnalysisServiceName = "$($mockSqlAnalysisName)`$$($mockNamedInstance_InstanceName)" + + $mockmockSourceCredentialUserName = "COMPANY\sqladmin" + $mockmockSourceCredentialPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force + $mockSourceCredential = New-Object System.Management.Automation.PSCredential( $mockmockSourceCredentialUserName, $mockmockSourceCredentialPassword ) + + #region Function mocks + $mockEmptyHashtable = { + return @() + } + + $mockGetService_DefaultInstance = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_DatabaseServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AgentServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FullTextServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_ReportingServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockDefaultInstance_IntegrationServiceName -f $mockCurrentSqlMajorVersion) -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AnalysisServiceName -PassThru -Force + ) + ) + } + + + $mockGetService_NamedInstance = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_DatabaseServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AgentServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_FullTextServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_ReportingServiceName -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockNamedInstance_IntegrationServiceName -f $mockCurrentSqlMajorVersion) -PassThru -Force + ), + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AnalysisServiceName -PassThru -Force + ) + ) + } + + $mockGetItemProperty_CallingWithWrongParameters = { + throw 'Mock Get-ItemProperty was called with wrong parameters' + } + + $mockRegistryPathSqlInstanceId = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' + $mockRegistryPathAnalysisServicesInstanceId = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\OLAP' + $mockGetItemProperty_SqlInstanceId = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockCurrentInstanceName -Value $mockCurrentDatabaseEngineInstanceId -PassThru -Force + ) + ) + } + + $mockGetItemProperty_SqlInstanceId_ParameterFilter = { + $Path -eq $mockRegistryPathSqlInstanceId -and + $Name -eq $mockCurrentInstanceName + } + + $mockGetItemProperty_AnalysisServicesInstanceId = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockCurrentInstanceName -Value $mockCurrentAnalysisServiceInstanceId -PassThru -Force + ) + ) + } + + $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter = { + $Path -eq $mockRegistryPathAnalysisServicesInstanceId -and + $Name -eq $mockCurrentInstanceName + } + + $mockGetItemProperty_DatabaseEngineSqlBinRoot = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'SQLBinRoot' -Value $mockCurrentDatabaseEngineSqlBinDirectory -PassThru -Force + ) + ) + } + + $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\setup" -and + $Name -eq 'SQLBinRoot' + } + + $mockGetItemProperty_AnalysisServicesSqlBinRoot = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'SQLBinRoot' -Value $mockCurrentAnalysisServicesSqlBinDirectory -PassThru -Force + ) + ) + } + + $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockCurrentAnalysisServiceInstanceId\setup" -and + $Name -eq 'SQLBinRoot' + } + + $mockGetItemProperty_IntegrationsServicesSqlPath = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'SQLPath' -Value $mockCurrentIntegrationServicesSqlPathDirectory -PassThru -Force + ) + ) + } + + $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\setup" -and + $Name -eq 'SQLPath' + } + + $mockGetNetFirewallRule = { + return @( + ( + New-CimInstance -ClassName 'MSFT_NetFirewallRule' -Property @{ + 'DisplayName' = $DisplayName + 'Enabled' = $true + 'Profile' = 'Any' + 'Direction' = 1 # 1 = Inbound, 2 = Outbound + } -Namespace 'root/standardcimv2' -ClientOnly + ) + ) + } + + $mockGetNetFirewallApplicationFilter = { + if ($AssociatedNetFirewallRule.DisplayName -eq "SQL Server Database Engine instance $mockCurrentInstanceName") + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Program' -Value (Join-Path $mockCurrentDatabaseEngineSqlBinDirectory -ChildPath $mockDatabaseEngineExecutableName) -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Integration Services Application') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Program' -Value (Join-Path -Path (Join-Path $mockCurrentIntegrationServicesSqlPathDirectory -ChildPath 'Binn') -ChildPath $mockIntegrationServicesExecutableName) -PassThru -Force + ) + ) + } + else + { + throw "Mock Get-NetFirewallApplicationFilter was called with a rule containing an unknown display name; $($AssociatedNetFirewallRule.DisplayName)" + } + } + + $mockGetNetFirewallServiceFilter = { + if ($AssociatedNetFirewallRule.DisplayName -eq "SQL Server Analysis Services instance $mockCurrentInstanceName") + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Service' -Value $mockCurrentSqlAnalysisServiceName -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Browser') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Service' -Value $mockSQLBrowserServiceName -PassThru -Force + ) + ) + } + else + { + throw "Mock Get-NetFirewallServiceFilter was called with a rule containing an unknown display name; $($AssociatedNetFirewallRule.DisplayName)" + } + } + + $mockGetNetFirewallPortFilter = { + if ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Reporting Services 80') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Protocol' -Value $mockFirewallRulePort_ReportingServicesNoSslProtocol -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalPort' -Value $mockFirewallRulePort_ReportingServicesNoSslLocalPort -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Reporting Services 443') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Protocol' -Value $mockFirewallRulePort_ReportingServicesSslProtocol -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalPort' -Value $mockFirewallRulePort_ReportingServicesSslLocalPort -PassThru -Force + ) + ) + } + elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Integration Services Port') + { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Protocol' -Value $mockFirewallRulePort_IntegrationServicesProtocol -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalPort' -Value $mockFirewallRulePort_IntegrationServicesLocalPort -PassThru -Force + ) + ) + } + else + { + throw "Mock Get-NetFirewallPortFilter was called with a rule containing an unknown display name; $($AssociatedNetFirewallRule.DisplayName)" + } + } + + $mockNewNetFirewallRule = { + if ( + ( + $DisplayName -eq "SQL Server Database Engine instance $mockCurrentInstanceName" -and + $Program -eq (Join-Path $mockCurrentDatabaseEngineSqlBinDirectory -ChildPath $mockDatabaseEngineExecutableName) + ) -or + ( + $DisplayName -eq 'SQL Server Browser' -and + $Service -eq $mockSQLBrowserServiceName + ) -or + ( + $DisplayName -eq "SQL Server Analysis Services instance $mockCurrentInstanceName" -and + $Service -eq $mockCurrentSqlAnalysisServiceName + ) -or + ( + $DisplayName -eq "SQL Server Reporting Services 80" -and + $Protocol -eq $mockFirewallRulePort_ReportingServicesNoSslProtocol -and + $LocalPort -eq $mockFirewallRulePort_ReportingServicesNoSslLocalPort + ) -or + ( + $DisplayName -eq "SQL Server Reporting Services 443" -and + $Protocol -eq $mockFirewallRulePort_ReportingServicesSslProtocol -and + $LocalPort -eq $mockFirewallRulePort_ReportingServicesSslLocalPort + ) -or + ( + $DisplayName -eq "SQL Server Integration Services Application" -and + $Program -eq (Join-Path -Path (Join-Path $mockCurrentIntegrationServicesSqlPathDirectory -ChildPath 'Binn') -ChildPath $mockIntegrationServicesExecutableName) + ) -or + ( + $DisplayName -eq "SQL Server Integration Services Port" -and + $Protocol -eq $mockFirewallRulePort_IntegrationServicesProtocol -and + $LocalPort -eq $mockFirewallRulePort_IntegrationServicesLocalPort + ) + ) + { + return + } + + throw "`nMock Get-NewFirewallRule was called with an unexpected rule configuration`n" + ` + "Display Name: $DisplayName`n" + ` + "Application: $Program`n" + ` + "Service: $Service`n" + ` + "Protocol: $Protocol`n" + ` + "Local port: $LocalPort`n" + } + + $mockGetItem_SqlMajorVersion = { + return New-Object Object | + Add-Member ScriptProperty VersionInfo { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ProductVersion' -Value ('{0}.0.0000.00000' -f $mockCurrentSqlMajorVersion) -PassThru -Force + } -PassThru -Force + } + + $mockGetItem_SqlMajorVersion_ParameterFilter = { + $Path -eq $mockCurrentPathToSetupExecutable + } + + #endregion Function mocks + + # Default parameters that are used for the It-blocks + $mockDefaultParameters = @{ + # These are written with both lower-case and upper-case to make sure we support that. + Features = 'SQLEngine,Rs,As,Is' + SourceCredential = $mockSourceCredential + } + + Describe "xSQLServerFirewall\Get-TargetResource" -Tag 'Get' { + # Local path to TestDrive:\ + $mockSourcePath = $TestDrive.FullName + + BeforeEach { + # General mocks + Mock -CommandName Get-Item -ParameterFilter $mockGetItem_SqlMajorVersion_ParameterFilter -MockWith $mockGetItem_SqlMajorVersion -Verifiable + + # Mock SQL Server Database Engine registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_SqlInstanceId -Verifiable + + # Mock SQL Server Analysis Services registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesInstanceId -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_DatabaseEngineSqlBinRoot -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesSqlBinRoot -Verifiable + + # Mock SQL Server Integration Services Registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter ` + -MockWith $mockGetItemProperty_IntegrationsServicesSqlPath -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_CallingWithWrongParameters -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + } + + $testProductVersion | ForEach-Object -Process { + $mockCurrentSqlMajorVersion = $_ + + $mockCurrentPathToSetupExecutable = Join-Path -Path $mockSourcePath -ChildPath $mockSetupExecutableName + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockDefaultInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + + $mockSqlInstallPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL" + $mockSqlBackupPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\Backup" + $mockSqlTempDatabasePath = '' + $mockSqlTempDatabaseLogPath = '' + $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + + Context "When SQL Server version is $mockCurrentSqlMajorVersion. Testing helper function Get-SqlRootPath" { + It 'Should return the the correct path for Database Engine' { + $result = Get-SQLPath -Feature 'SQLEngine' -InstanceName $mockDefaultInstance_InstanceName + $result | Should Be $mockCurrentDatabaseEngineSqlBinDirectory + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + + It 'Should return the the correct path for Analysis Services' { + $result = Get-SQLPath -Feature 'As' -InstanceName $mockDefaultInstance_InstanceName + $result | Should Be $mockCurrentAnalysisServicesSqlBinDirectory + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + + It 'Should return the the correct path for Integration Services' { + $result = Get-SQLPath -Feature 'Is' -InstanceName $mockDefaultInstance_InstanceName -SQLVersion $mockCurrentSqlMajorVersion + $result | Should Be $mockCurrentIntegrationServicesSqlPathDirectory + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and there is no components installed" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Test-IsFirewallRuleInDesiredState -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + } + + It 'Should not return any values in the read parameters' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should BeNullOrEmpty + $result.BrowserFirewall | Should BeNullOrEmpty + $result.ReportingServicesFirewall | Should BeNullOrEmpty + $result.AnalysisServicesFirewall | Should BeNullOrEmpty + $result.IntegrationServicesFirewall | Should BeNullOrEmpty + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + $result.Features | Should BeNullOrEmpty + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-IsFirewallRuleInDesiredState -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + $result.Features | Should Be $testParameters.Features + } + + It 'Should return $false for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $false + } + + It 'Should return $false for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $false + } + + It 'Should return $false for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $false + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + } + + It 'Should return $true for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $true + } + + It 'Should return $true for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $true + } + + It 'Should return $true for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $true + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + $result.Features | Should Be $testParameters.Features + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + $mockCurrentInstanceName = $mockNamedInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockNamedInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + $result.Features | Should Be $testParameters.Features + } + + It 'Should return $false for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $false + } + + It 'Should return $false for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $false + } + + It 'Should return $false for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $false + } + + It 'Should return $false for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $false + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.SourcePath | Should Be $testParameters.SourcePath + } + + It 'Should return $true for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $true + } + + It 'Should return $true for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $true + } + + It 'Should return $true for the read parameter ReportingServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.ReportingServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $true + } + + It 'Should return $true for the read parameter IntegrationServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.IntegrationServicesFirewall | Should Be $true + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + $result.Features | Should Be $testParameters.Features + } + + It 'Should call the correct functions exact number of times' { + $result = Get-TargetResource @testParameters + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + } + } + + Describe "xSQLServerFirewall\Set-TargetResource" -Tag 'Set' { + # Local path to TestDrive:\ + $mockSourcePath = $TestDrive.FullName + + BeforeEach { + # General mocks + Mock -CommandName Get-Item -ParameterFilter $mockGetItem_SqlMajorVersion_ParameterFilter -MockWith $mockGetItem_SqlMajorVersion -Verifiable + + # Mock SQL Server Database Engine registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_SqlInstanceId -Verifiable + + # Mock SQL Server Analysis Services registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesInstanceId -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_DatabaseEngineSqlBinRoot -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesSqlBinRoot -Verifiable + + # Mock SQL Server Integration Services Registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter ` + -MockWith $mockGetItemProperty_IntegrationsServicesSqlPath -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_CallingWithWrongParameters -Verifiable + Mock -CommandName New-NetFirewallRule -MockWith $mockNewNetFirewallRule -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + } + + $testProductVersion | ForEach-Object -Process { + $mockCurrentSqlMajorVersion = $_ + + $mockCurrentPathToSetupExecutable = Join-Path -Path $mockSourcePath -ChildPath $mockSetupExecutableName + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockDefaultInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + + $mockSqlInstallPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL" + $mockSqlBackupPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\Backup" + $mockSqlTempDatabasePath = '' + $mockSqlTempDatabaseLogPath = '' + $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + + # Mock this here because only the first test uses it. + Mock -CommandName Test-TargetResource -MockWith { return $false } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and there is no components installed" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Test-IsFirewallRuleInDesiredState -Verifiable + Mock -CommandName New-NetFirewallRule -Verifiable + + Mock New-TerminatingError -MockWith { return $ErrorType } + } + + It 'Should throw the correct error when Set-TargetResource verifies result with Test-TargetResource' { + { Set-TargetResource @testParameters } | Should Throw TestFailedAfterSet + + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-IsFirewallRuleInDesiredState -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 0 -Scope It + } + } + + # Mock this here so the rest of the test uses it. + Mock -CommandName Test-TargetResource -MockWith { return $true } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should create all firewall rules without throwing' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 14 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 2 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should not call mock New-NetFirewallRule' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + + $mockCurrentInstanceName = $mockNamedInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockNamedInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is not in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should create all firewall rules without throwing' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 14 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 2 -Scope It + } + } + + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for named instance" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_NamedInstance -Verifiable + } + + It 'Should not call mock New-NetFirewallRule' { + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled -CommandName New-NetFirewallRule -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-Service -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallRule -Exactly -Times 8 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallApplicationFilter -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallServiceFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-NetFirewallPortFilter -Exactly -Times 3 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter -Exactly -Times 1 -Scope It + } + } + } + } + + Describe "xSQLServerFirewall\Test-TargetResource" -Tag 'Test' { + # Local path to TestDrive:\ + $mockSourcePath = $TestDrive.FullName + + BeforeEach { + # General mocks + Mock -CommandName Get-Item -ParameterFilter $mockGetItem_SqlMajorVersion_ParameterFilter -MockWith $mockGetItem_SqlMajorVersion -Verifiable + + # Mock SQL Server Database Engine registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_SqlInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_SqlInstanceId -Verifiable + + # Mock SQL Server Analysis Services registry for Instance ID. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesInstanceId_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesInstanceId -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_DatabaseEngineSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_DatabaseEngineSqlBinRoot -Verifiable + + # Mocking SQL Server Database Engine registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_AnalysisServicesSqlBinRoot_ParameterFilter ` + -MockWith $mockGetItemProperty_AnalysisServicesSqlBinRoot -Verifiable + + # Mock SQL Server Integration Services Registry for path to binaries root. + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_IntegrationsServicesSqlPath_ParameterFilter ` + -MockWith $mockGetItemProperty_IntegrationsServicesSqlPath -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_CallingWithWrongParameters -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + } + + $mockCurrentSqlMajorVersion = $_ + + $mockCurrentPathToSetupExecutable = Join-Path -Path $mockSourcePath -ChildPath $mockSetupExecutableName + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + $mockCurrentDatabaseEngineInstanceId = "$($mockSqlDatabaseEngineInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + $mockCurrentAnalysisServiceInstanceId = "$($mockSqlAnalysisServicesInstanceIdName)$($mockCurrentSqlMajorVersion).$($mockCurrentInstanceName)" + + $mockCurrentSqlAnalysisServiceName = $mockDefaultInstance_AnalysisServiceName + + $mockCurrentDatabaseEngineSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\MSSQL\Binn" + $mockCurrentAnalysisServicesSqlBinDirectory = "C:\Program Files\Microsoft SQL Server\$mockCurrentDatabaseEngineInstanceId\OLAP\Binn" + $mockCurrentIntegrationServicesSqlPathDirectory = "C:\Program Files\Microsoft SQL Server\$($mockCurrentSqlMajorVersion)0\DTS\" + + + $mockSqlInstallPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL" + $mockSqlBackupPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\Backup" + $mockSqlTempDatabasePath = '' + $mockSqlTempDatabaseLogPath = '' + $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" + + Context "When the system is not in the desired state" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return $false from Test-TargetResource' { + $resultTestTargetResource = Test-TargetResource @testParameters + $resultTestTargetResource | Should Be $false + } + } + + Context "When the system is in the desired state" { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockCurrentInstanceName + SourcePath = $mockSourcePath + } + + Mock -CommandName Get-NetFirewallRule -MockWith $mockGetNetFirewallRule -Verifiable + Mock -CommandName Get-NetFirewallApplicationFilter -MockWith $mockGetNetFirewallApplicationFilter -Verifiable + Mock -CommandName Get-NetFirewallServiceFilter -MockWith $mockGetNetFirewallServiceFilter -Verifiable + Mock -CommandName Get-NetFirewallPortFilter -MockWith $mockGetNetFirewallPortFilter -Verifiable + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should return $true from Test-TargetResource' { + $resultTestTargetResource = Test-TargetResource @testParameters + $resultTestTargetResource | Should Be $true + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 index 290752bbc..1ef784f43 100644 --- a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 @@ -1,465 +1,905 @@ # Suppressing this rule because PlainText is required for one of the functions used in this test [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] -param() - -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerLogin' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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\')) + +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerLogin' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + 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 ) + + # Create PSCredential object for SQL Logins + $mockSqlLoginUser = 'dba' + $mockSqlLoginPassword = 'P@ssw0rd-12P@ssw0rd-12' | ConvertTo-SecureString -AsPlainText -Force + $mockSqlLoginCredential = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginPassword ) + + $mockSqlLoginBadPassword = 'pw' | ConvertTo-SecureString -AsPlainText -Force + $mockSqlLoginCredentialBadPassword = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginBadPassword ) + + $mockSqlLoginReusedPassword = 'reused' | ConvertTo-SecureString -AsPlainText -Force + $mockSqlLoginCredentialReusedPassword = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginReusedPassword ) + + $mockSqlLoginOtherPassword = 'other' | ConvertTo-SecureString -AsPlainText -Force + $mockSqlLoginCredentialOtherPassword = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginOtherPassword ) + + $instanceParameters = @{ + SQLInstanceName = 'MSSQLSERVER' + SQLServer = 'Server1' } - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + $getTargetResource_UnknownSqlLogin = $instanceParameters.Clone() + $getTargetResource_UnknownSqlLogin.Add( 'Name','UnknownSqlLogin' ) + + $getTargetResource_UnknownWindows = $instanceParameters.Clone() + $getTargetResource_UnknownWindows.Add( 'Name','Windows\UserOrGroup' ) + + $getTargetResource_KnownSqlLogin = $instanceParameters.Clone() + $getTargetResource_KnownSqlLogin.Add( 'Name','SqlLogin1' ) + + $getTargetResource_KnownWindowsUser = $instanceParameters.Clone() + $getTargetResource_KnownWindowsUser.Add( 'Name','Windows\User1' ) + + $getTargetResource_KnownWindowsGroup = $instanceParameters.Clone() + $getTargetResource_KnownWindowsGroup.Add( 'Name','Windows\Group1' ) + + $testTargetResource_WindowsUserAbsent = $instanceParameters.Clone() + $testTargetResource_WindowsUserAbsent.Add( 'Name','Windows\UserAbsent' ) + $testTargetResource_WindowsUserAbsent.Add( 'LoginType','WindowsUser' ) + + $testTargetResource_WindowsGroupAbsent = $instanceParameters.Clone() + $testTargetResource_WindowsGroupAbsent.Add( 'Name','Windows\GroupAbsent' ) + $testTargetResource_WindowsGroupAbsent.Add( 'LoginType','WindowsGroup' ) + + $testTargetResource_SqlLoginAbsent = $instanceParameters.Clone() + $testTargetResource_SqlLoginAbsent.Add( 'Name','SqlLoginAbsent' ) + $testTargetResource_SqlLoginAbsent.Add( 'LoginType','SqlLogin' ) + + $testTargetResource_WindowsUserPresent = $instanceParameters.Clone() + $testTargetResource_WindowsUserPresent.Add( 'Name','Windows\User1' ) + $testTargetResource_WindowsUserPresent.Add( 'LoginType','WindowsUser' ) + + $testTargetResource_WindowsGroupPresent = $instanceParameters.Clone() + $testTargetResource_WindowsGroupPresent.Add( 'Name','Windows\Group1' ) + $testTargetResource_WindowsGroupPresent.Add( 'LoginType','WindowsGroup' ) + + $testTargetResource_SqlLoginPresentWithDefaultValues = $instanceParameters.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValues.Add( 'Name','SqlLogin1' ) + $testTargetResource_SqlLoginPresentWithDefaultValues.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_CertificateAbsent = $instanceParameters.Clone() + $setTargetResource_CertificateAbsent.Add( 'Name','Certificate' ) + $setTargetResource_CertificateAbsent.Add( 'LoginType','Certificate' ) + + $setTargetResource_WindowsUserAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsUserAbsent.Add( 'Name','Windows\UserAbsent' ) + $setTargetResource_WindowsUserAbsent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_WindowsGroupAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsGroupAbsent.Add( 'Name','Windows\GroupAbsent' ) + $setTargetResource_WindowsGroupAbsent.Add( 'LoginType','WindowsGroup' ) + + $setTargetResource_SqlLoginAbsent = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsent.Add( 'Name','SqlLoginAbsent' ) + $setTargetResource_SqlLoginAbsent.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentExisting = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentExisting.Add( 'Name','Existing' ) + $setTargetResource_SqlLoginAbsentExisting.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentUnknown = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentUnknown.Add( 'Name','Unknown' ) + $setTargetResource_SqlLoginAbsentUnknown.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_WindowsUserPresent = $instanceParameters.Clone() + $setTargetResource_WindowsUserPresent.Add( 'Name','Windows\User1' ) + $setTargetResource_WindowsUserPresent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_CertificateAbsent = $instanceParameters.Clone() + $setTargetResource_CertificateAbsent.Add( 'Name','Certificate' ) + $setTargetResource_CertificateAbsent.Add( 'LoginType','Certificate' ) + + $setTargetResource_WindowsUserAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsUserAbsent.Add( 'Name','Windows\UserAbsent' ) + $setTargetResource_WindowsUserAbsent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_WindowsGroupAbsent = $instanceParameters.Clone() + $setTargetResource_WindowsGroupAbsent.Add( 'Name','Windows\GroupAbsent' ) + $setTargetResource_WindowsGroupAbsent.Add( 'LoginType','WindowsGroup' ) + + $setTargetResource_SqlLoginAbsent = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsent.Add( 'Name','SqlLoginAbsent' ) + $setTargetResource_SqlLoginAbsent.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentExisting = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentExisting.Add( 'Name','Existing' ) + $setTargetResource_SqlLoginAbsentExisting.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_SqlLoginAbsentUnknown = $instanceParameters.Clone() + $setTargetResource_SqlLoginAbsentUnknown.Add( 'Name','Unknown' ) + $setTargetResource_SqlLoginAbsentUnknown.Add( 'LoginType','SqlLogin' ) + + $setTargetResource_WindowsUserPresent = $instanceParameters.Clone() + $setTargetResource_WindowsUserPresent.Add( 'Name','Windows\User1' ) + $setTargetResource_WindowsUserPresent.Add( 'LoginType','WindowsUser' ) + + $setTargetResource_WindowsGroupPresent = $instanceParameters.Clone() + $setTargetResource_WindowsGroupPresent.Add( 'Name','Windows\Group1' ) + $setTargetResource_WindowsGroupPresent.Add( 'LoginType','WindowsGroup' ) + + $setTargetResource_SqlLoginPresent = $instanceParameters.Clone() + $setTargetResource_SqlLoginPresent.Add( 'Name','SqlLogin1' ) + $setTargetResource_SqlLoginPresent.Add( 'LoginType','SqlLogin' ) + + $mockConnectSql = { + $windowsUser = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Windows\User1' ) + $windowsUser.LoginType = 'WindowsUser' + $windowsGroup = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Windows\Group1' ) + $windowsGroup.LoginType = 'windowsGroup' + $sqlLogin = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'SqlLogin1' ) + $sqlLogin.LoginType = 'SqlLogin' + $sqlLogin.MustChangePassword = $false + $sqlLogin.PasswordPolicyEnforced = $true + $sqlLogin.PasswordExpirationEnabled = $true + + $mock = New-Object PSObject -Property @{ + LoginMode = 'Mixed' + Logins = @{ + $windowsUser.Name = $windowsUser + $windowsGroup.Name = $windowsGroup + $sqlLogin.Name = $sqlLogin + } + } + + return $mock + } - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit + #endregion Pester Test Initialization - #endregion HEADER + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Verifiable -Scope Describe - # Begin Testing - try - { - #region Pester Test Initialization + Context 'When the login is Absent' { - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + It 'Should be Absent when an unknown SQL Login is provided' { + ( Get-TargetResource @getTargetResource_UnknownSqlLogin ).Ensure | Should Be 'Absent' - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - $mockSqlLoginUser = "dba" - $mockSqlLoginPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force - $mockSqlLoginCredential = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginPassword ) + It 'Should be Absent when an unknown Windows User or Group is provided' { + ( Get-TargetResource @getTargetResource_UnknownWindows ).Ensure | Should Be 'Absent' - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } } - #endregion Pester Test Initialization - - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - } + Context 'When the login is Present' { + It 'Should be Present when a known SQL Login is provided' { + $result = Get-TargetResource @getTargetResource_KnownSqlLogin - $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'SqlLogin' + $result.LoginMustChangePassword | Should Not BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should Not BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should Not BeNullOrEmpty - It 'Should not return the state as absent' { - $result.Ensure | Should Be 'Absent' - $result.LoginType | Should Be '' - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should be Present when a known Windows User is provided' { + $result = Get-TargetResource @getTargetResource_KnownWindowsUser - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsUser' + $result.LoginMustChangePassword | Should BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - - Context 'When the system is in the desired state for a Windows user' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - } - - $result = Get-TargetResource @testParameters - It 'Should not return the state as present' { - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'WindowsUser' - } + It 'Should be Present when a known Windows User is provided' { + $result = Get-TargetResource @getTargetResource_KnownWindowsGroup - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsGroup' + $result.LoginMustChangePassword | Should BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } + } + } - Context 'When the system is in the desired state for a Windows group' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - } - - $result = Get-TargetResource @testParameters + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'WindowsGroup' - } + Context 'When the desired state is Absent' { + It 'Should return $true when the specified Windows user is Absent' { + $testTargetResource_WindowsUserAbsent_EnsureAbsent = $testTargetResource_WindowsUserAbsent.Clone() + $testTargetResource_WindowsUserAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsureAbsent ) | Should Be $true - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - Context 'When the system is in the desired state for a SQL login' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - } - - $result = Get-TargetResource @testParameters + It 'Should return $true when the specified Windows group is Absent' { + $testTargetResource_WindowsGroupAbsent_EnsureAbsent = $testTargetResource_WindowsGroupAbsent.Clone() + $testTargetResource_WindowsGroupAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'SqlLogin' - } + ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsureAbsent ) | Should Be $true - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return $true when the specified SQL Login is Absent' { + $testTargetResource_SqlLoginAbsent_EnsureAbsent = $testTargetResource_SqlLoginAbsent.Clone() + $testTargetResource_SqlLoginAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_SqlLoginAbsent_EnsureAbsent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified Windows user is Present' { + $testTargetResource_WindowsUserPresent_EnsureAbsent = $testTargetResource_WindowsUserPresent.Clone() + $testTargetResource_WindowsUserPresent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_WindowsUserPresent_EnsureAbsent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified Windows group is Present' { + $testTargetResource_WindowsGroupPresent_EnsureAbsent = $testTargetResource_WindowsGroupPresent.Clone() + $testTargetResource_WindowsGroupPresent_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_WindowsGroupPresent_EnsureAbsent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - Assert-VerifiableMocks + It 'Should return $false when the specified SQL Login is Present' { + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent.Add( 'Ensure','Absent' ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } } + + Context 'When the desired state is Present' { + It 'Should return $false when the specified Windows user is Absent' { + $testTargetResource_WindowsUserAbsent_EnsurePresent = $testTargetResource_WindowsUserAbsent.Clone() + $testTargetResource_WindowsUserAbsent_EnsurePresent.Add( 'Ensure','Present' ) - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should return the state as absent when desired windows user does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - } + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsurePresent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + It 'Should return $false when the specified Windows group is Absent' { + $testTargetResource_WindowsGroupAbsent_EnsurePresent = $testTargetResource_WindowsGroupAbsent.Clone() + $testTargetResource_WindowsGroupAbsent_EnsurePresent.Add( 'Ensure','Present' ) - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsurePresent ) | Should Be $false - It 'Should return the state as present when desired login exists and login type is SQL login' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'SqlLogin' - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + It 'Should return $false when the specified SQL Login is Absent' { + $testTargetResource_SqlLoginAbsent_EnsurePresent = $testTargetResource_SqlLoginAbsent.Clone() + $testTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + ( Test-TargetResource @testTargetResource_SqlLoginAbsent_EnsurePresent ) | Should Be $false - It 'Should return the state as present when desired login exists and login type is Windows' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'WindowsUser' - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + It 'Should return $true when the specified Windows user is Present' { + $testTargetResource_WindowsUserPresent_EnsurePresent = $testTargetResource_WindowsUserPresent.Clone() + $testTargetResource_WindowsUserPresent_EnsurePresent.Add( 'Ensure','Present' ) - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + ( Test-TargetResource @testTargetResource_WindowsUserPresent_EnsurePresent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - Context 'When the system is in the desired state' { - It 'Should return the state as present when desired windows user exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - } + It 'Should return $true when the specified Windows group is Present' { + $testTargetResource_WindowsGroupPresent_EnsurePresent = $testTargetResource_WindowsGroupPresent.Clone() + $testTargetResource_WindowsGroupPresent_EnsurePresent.Add( 'Ensure','Present' ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsGroupPresent_EnsurePresent ) | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return the state as present when desired windows group exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'WindowsGroup' - } + It 'Should return $true when the specified SQL Login is Present using default parameter values' { + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent.Add( 'Ensure','Present' ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent ) | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return the state as present when desired sql login exists' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'SqlLogin' - } + It 'Should return $true when the specified SQL Login is Present and PasswordExpirationEnabled is $true' { + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent.Add( 'LoginPasswordExpirationEnabled',$true ) - $result = Test-TargetResource @testParameters - $result | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent ) | Should Be $true - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified SQL Login is Present and PasswordExpirationEnabled is $false' { + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent.Add( 'LoginPasswordExpirationEnabled',$false ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } - Assert-VerifiableMocks + It 'Should return $true when the specified SQL Login is Present and PasswordPolicyEnforced is $true' { + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent.Add( 'LoginPasswordPolicyEnforced',$true ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified SQL Login is Present and PasswordPolicyEnforced is $false' { + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent.Add( 'LoginPasswordPolicyEnforced',$false ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $true when the specified SQL Login is Present using default parameter values and the password is properly configured.' { + $testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent ) | Should Be $true + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 2 -Exactly + } + + It 'Should return $false when the specified SQL Login is Present using default parameter values and the password is not properly configured.' { + Mock -CommandName Connect-SQL -MockWith { throw } -ModuleName $script:DSCResourceName -Scope It -Verifiable -ParameterFilter { $SetupCredential } + + $testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() + $testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent.Add( 'Ensure','Present' ) + $testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredentialBadpassword ) + + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent ) | Should Be $false + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 2 -Exactly + } } + } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\Stacy') -Property @{ LoginType = 'WindowsUser'} ) ) - 'John' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'John') -Property @{ LoginType = 'SqlLogin'} ) ) - 'COMPANY\SqlUsers' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'COMPANY\SqlUsers') -Property @{ LoginType = 'WindowsGroup'} ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownSqlLogin' - LoginType = 'SqlLogin' - } + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + Mock -CommandName Update-SQLServerLogin -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName New-SQLServerLogin -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName Remove-SQLServerLogin -MockWith {} -ModuleName $script:DSCResourceName + Mock -CommandName Set-SQLServerLoginPassword -MockWith {} -ModuleName $script:DSCResourceName + + Context 'When the desired state is Absent' { + It 'Should drop the specified Windows User when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsUserPresent_EnsureAbsent = $setTargetResource_WindowsUserPresent.Clone() + $setTargetResource_WindowsUserPresent_EnsureAbsent.Add( 'Ensure','Absent' ) + + Set-TargetResource @setTargetResource_WindowsUserPresent_EnsureAbsent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { - { Set-TargetResource @testParameters } | Should Throw - Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should drop the specified Windows Group when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsGroupPresent_EnsureAbsent = $setTargetResource_WindowsGroupPresent.Clone() + $setTargetResource_WindowsGroupPresent_EnsureAbsent.Add( 'Ensure','Absent' ) - $testParameters += @{ - LoginCredential = $mockSqlLoginCredential - } + Set-TargetResource @setTargetResource_WindowsGroupPresent_EnsureAbsent - It 'Should not throw an error when desired login type is a SQL login' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'SqlLogin' - } - } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should drop the specified SQL Login when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsureAbsent = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsureAbsent.Add( 'Ensure','Absent' ) - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownUser' - LoginType = 'WindowsUser' - } + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsureAbsent - It 'Should not throw an error when desired login type is a Windows User' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsUser' - } - } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should do nothing when the specified Windows User is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindwsUserAbsent_EnsureAbsent = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindwsUserAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\UnknownGroup' - LoginType = 'WindowsGroup' - } + Set-TargetResource @setTargetResource_WindwsUserAbsent_EnsureAbsent - It 'Should not throw an error when desired login type is a Windows Group' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsGroup' - } - } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should do nothing when the specified Windows Group is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindwsGroupAbsent_EnsureAbsent = $setTargetResource_WindowsGroupAbsent.Clone() + $setTargetResource_WindwsGroupAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Name = 'COMPANY\Stacy' - } + Set-TargetResource @setTargetResource_WindwsGroupAbsent_EnsureAbsent - It 'Should call the function Remove-SqlLogin when desired state should be absent' { - # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Absent' - } - } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + It 'Should do nothing when the specified SQL Login is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsureAbsent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - Set-TargetResource @testParameters + Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsureAbsent - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlLogin -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly } + } - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'John' - LoginType = 'SqlLogin' + Context 'When the desired state is Present' { + It 'Should add the specified Windows User when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsUserAbsent_EnsurePresent = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindowsUserAbsent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsUserAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should add the specified Windows Group when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsGroupAbsent_EnsurePresent = $setTargetResource_WindowsGroupAbsent.Clone() + $setTargetResource_WindowsGroupAbsent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsGroupAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should add the specified SQL Login when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + + Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should add the specified SQL Login when it is Absent and MustChangePassword is $false' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginMustChangePassword',$false ) + + Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error when adding an unsupported login type' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_CertificateAbsent_EnsurePresent = $setTargetResource_CertificateAbsent.Clone() + $setTargetResource_CertificateAbsent_EnsurePresent.Add( 'Ensure','Present' ) + + { Set-TargetResource @setTargetResource_CertificateAbsent_EnsurePresent } | Should Throw 'LoginTypeNotImplemented' + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error when adding the specified SQL Login when it is Absent and is missing the LoginCredential parameter' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent_NoCred = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent_NoCred.Add( 'Ensure','Present' ) + + { Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent_NoCred } | Should Throw 'LoginCredentialNotFound' + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should do nothing if the specified Windows User is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsUserPresent_EnsurePresent = $setTargetResource_WindowsUserPresent.Clone() + $setTargetResource_WindowsUserPresent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsUserPresent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should do nothing if the specified Windows Group is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_WindowsGroupPresent_EnsurePresent = $setTargetResource_WindowsGroupPresent.Clone() + $setTargetResource_WindowsGroupPresent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsGroupPresent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + + It 'Should update the password of the specified SQL Login if it is Present and all parameters match' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsurePresent = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginPresent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 1 -Exactly + } + + It 'Should set PasswordExpirationEnabled on the specified SQL Login if it does not match the LoginPasswordExpirationEnabled parameter' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled.Add( 'LoginCredential',$mockSqlLoginCredential ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled.Add( 'LoginPasswordExpirationEnabled',$false ) + + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordExpirationEnabled + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 1 -Exactly + } + + It 'Should set PasswordPolicyEnforced on the specified SQL Login if it does not match the LoginPasswordPolicyEnforced parameter' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced.Add( 'LoginCredential',$mockSqlLoginCredential ) + $setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced.Add( 'LoginPasswordPolicyEnforced',$false ) + + Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent_LoginPasswordPolicyEnforced + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 1 -Exactly + } + + It 'Should throw the correct error when creating a SQL Login if the LoginMode is not Mixed' { + $mockConnectSQL_LoginModeNormal = { + return New-Object Object | + Add-Member ScriptProperty Logins { + return @{ + 'Windows\User1' = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'Windows\User1' -PassThru | + Add-Member -MemberType NoteProperty -Name 'LoginType' -Value 'WindowsUser' -PassThru | + Add-Member -MemberType ScriptMethod -Name Alter -Value {} -PassThru | + Add-Member -MemberType ScriptMethod -Name Drop -Value {} -PassThru -Force + ) + 'SqlLogin1' = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'SqlLogin1' -PassThru | + Add-Member -MemberType NoteProperty -Name 'LoginType' -Value 'SqlLogin' -PassThru | + Add-Member -MemberType NoteProperty -Name 'MustChangePassword' -Value $false -PassThru | + Add-Member -MemberType NoteProperty -Name 'PasswordExpirationEnabled' -Value $true -PassThru | + Add-Member -MemberType NoteProperty -Name 'PasswordPolicyEnforced' -Value $true -PassThru | + Add-Member -MemberType ScriptMethod -Name Alter -Value {} -PassThru | + Add-Member -MemberType ScriptMethod -Name Drop -Value {} -PassThru -Force + ) + 'Windows\Group1' = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'Windows\Group1' -PassThru | + Add-Member -MemberType NoteProperty -Name 'LoginType' -Value 'WindowsGroup' -PassThru | + Add-Member -MemberType ScriptMethod -Name Alter -Value {} -PassThru | + Add-Member -MemberType ScriptMethod -Name Drop -Value {} -PassThru -Force + ) + } + } -PassThru | + Add-Member -MemberType NoteProperty -Name LoginMode -Value 'Normal' -PassThru -Force + } + + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL_LoginModeNormal -ModuleName $script:DSCResourceName -Scope It -Verifiable + + $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) + $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + + { Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent } | Should Throw 'IncorrectLoginMode' + + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } + } + } + + InModuleScope -ModuleName $script:DSCResourceName { + Describe "$($script:DSCResourceName)\Update-SQLServerLogin" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + + Context 'When the Login is altered' { + It 'Should silently alter the login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + + { Update-SQLServerLogin -Login $login } | Should Not Throw } - It 'Should throw an error when desired login type is a SQL login and LoginCredential parameter is not passed' { - { Set-TargetResource @testParameters } | Should Throw - Assert-MockCalled Connect-SQL -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + It 'Should throw the correct error when altering the login fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'SqlLogin' + + { Update-SQLServerLogin -Login $login } | Should Throw 'AlterLoginFailed' } + } + } + + Describe "$($script:DSCResourceName)\New-SQLServerLogin" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + + Context 'When the Login is created' { + It 'Should silently create a Windows login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'WindowsUser' + + { New-SQLServerLogin -Login $login } | Should Not Throw + } + + It 'Should silently create a SQL login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + $login.LoginType = 'SqlLogin' + $login.MockLoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force + LoginCreateOptions = 'None' + } - $testParameters += @{ - LoginCredential = $mockSqlLoginCredential + { New-SQLServerLogin @createLoginParams } | Should Not Throw } - It 'Should not throw an error when desired login type is a SQL login' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'SqlLogin' - } - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should throw the correct error when login creation fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'SqlLogin' - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + { New-SQLServerLogin -Login $login } | Should Throw 'LoginCreationFailedWindowsNotSpecified' } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\Stacy' - LoginType = 'WindowsUser' + It 'Should throw the correct error when password validation fails when creating a SQL Login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + $login.LoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'pw' -AsPlainText -Force + LoginCreateOptions = 'None' + } + + { New-SQLServerLogin @createLoginParams } | Should Throw 'PasswordValidationFailed' } - It 'Should not throw an error when desired login type is a Windows User' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsUser' - } - } -ModuleName $script:DSCResourceName -Verifiable + It 'Should throw the correct error when creating a SQL Login fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Existing' ) + $login.LoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force + LoginCreateOptions = 'None' + } - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + { New-SQLServerLogin @createLoginParams } | Should Throw 'LoginCreationFailedFailedOperation' } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'COMPANY\SqlUsers' - LoginType = 'WindowsGroup' + It 'Should throw the correct error when creating a SQL Login fails with an unhandled exception' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Unknown' ) + $login.LoginType = 'SqlLogin' + + $createLoginParams = @{ + Login = $login + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force + LoginCreateOptions = 'None' + } + + { New-SQLServerLogin @createLoginParams } | Should Throw 'LoginCreationFailedSqlNotSpecified' } + } + } - It 'Should not throw an error when desired login type is a Windows Group' { - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' - LoginType = 'WindowsGroup' - } - } -ModuleName $script:DSCResourceName -Verifiable + Describe "$($script:DSCResourceName)\Remove-SQLServerLogin" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName + + Context 'When the Login is dropped' { + It 'Should silently drop the login' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' - { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + { Remove-SQLServerLogin -Login $login } | Should Not Throw } - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Name = 'COMPANY\UnknownUser' - LoginType = 'SqlLogin' + It 'Should throw the correct error when dropping the login fails' { + $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Domain\User' ) + $login.LoginType = 'WindowsUser' + $login.MockLoginType = 'SqlLogin' + + { Remove-SQLServerLogin -Login $login } | Should Throw 'DropLoginFailed' } + } + } - It 'Should not call the function Remove-SqlLogin when desired state is already absent' { - # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the removal (in this case) was successful. - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Absent' - } - } -ModuleName $script:DSCResourceName -Verifiable + Describe "$($script:DSCResourceName)\Set-SQLServerLoginPassword" { + Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName - Mock -CommandName Remove-SqlLogin -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Context 'When the password is set on an existing login' { + It 'Should silently set the password' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force + } + + { Set-SQLServerLoginPassword @setPwParams } | Should Not Throw + } - Set-TargetResource @testParameters + It 'Should throw the correct error when password validation fails' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'pw' -AsPlainText -Force + } + + { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordValidationFailed' + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlLogin -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It + It 'Should throw the correct error when changing the password fails' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'reused' -AsPlainText -Force + } + + { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordChangeFailed' } - } - Assert-VerifiableMocks + It 'Should throw the correct error when changing the password fails' { + $setPwParams = @{ + Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) + SecureString = ConvertTo-SecureString -String 'other' -AsPlainText -Force + } + + { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordChangeFailed' + } + } } } - finally - { - #region FOOTER +} +finally +{ + #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - } + #endregion } - -$testJob | Receive-Job -Wait diff --git a/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 index 1e31dee8d..273563bad 100644 --- a/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 @@ -1,58 +1,137 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerPermission' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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\')) +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerPermission' + +#region HEADER + +# Unit Test Template Version: 1.1.0 +[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit + +#endregion HEADER + +# Begin Testing +try +{ + #region Pester Test Initialization + + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + + $nodeName = 'localhost' + $instanceName = 'DEFAULT' + $principal = 'COMPANY\SqlServiceAcct' + $permission = @( 'AlterAnyAvailabilityGroup','ViewServerState' ) + + #endregion Pester Test Initialization + + $defaultParameters = @{ + InstanceName = $instanceName + NodeName = $nodeName + Principal = $principal + Permission = $permission } - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Context 'When the system is not in the desired state' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - #endregion HEADER + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters - # Begin Testing - try - { - #region Pester Test Initialization + $result = Get-TargetResource @testParameters - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + It 'Should return the desired state as absent' { + $result.Ensure | Should Be 'Absent' + } - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $principal = 'COMPANY\SqlServiceAcct' - $permission = @( 'AlterAnyAvailabilityGroup','ViewServerState' ) + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $nodeName + $result.InstanceName | Should Be $instanceName + $result.Principal | Should Be $principal + } - #endregion Pester Test Initialization + It 'Should not return any permissions' { + $result.Permission | Should Be $null + } - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Principal = $principal - Permission = $permission + It 'Should call the mock function Get-SQLPSInstance' { + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } + + Context 'When the system is in the desired state' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal + + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + + $result = Get-TargetResource @testParameters + + It 'Should return the desired state as present' { + $result.Ensure | Should Be 'Present' + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Context 'When the system is not in the desired state' { + It 'Should return the same values as passed as parameters' { + $result.NodeName | Should Be $nodeName + $result.InstanceName | Should Be $instanceName + $result.Principal | Should Be $principal + } + + It 'Should return the permissions passed as parameter' { + foreach ($currentPermission in $permission) { + if( $result.Permission -ccontains $currentPermission ) { + $permissionState = $true + } else { + $permissionState = $false + break + } + } + + $permissionState | Should Be $true + } + + It 'Should call the mock function Get-SQLPSInstance' { + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } + + Assert-VerifiableMocks + } + + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Context 'When the system is not in the desired state' { + It 'Should return that desired state is absent when wanted desired state is to be Present' { Mock -CommandName Get-SQLPSInstance -MockWith { [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false @@ -67,29 +146,44 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { } -ModuleName $script:DSCResourceName -Verifiable $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + } - $result = Get-TargetResource @testParameters + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return the desired state as absent' { - $result.Ensure | Should Be 'Absent' - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $nodeName - $result.InstanceName | Should Be $instanceName - $result.Principal | Should Be $principal - } + It 'Should return that desired state is absent when wanted desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - It 'Should not return any permissions' { - $result.Permission | Should Be $null - } + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - It 'Should call the mock function Get-SQLPSInstance' { - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Context 'When the system is in the desired state' { + } + + Context 'When the system is in the desired state' { + It 'Should return that desired state is present when wanted desired state is to be Present' { Mock -CommandName Get-SQLPSInstance -MockWith { [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true @@ -104,260 +198,154 @@ $testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { } -ModuleName $script:DSCResourceName -Verifiable $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + } - $result = Get-TargetResource @testParameters + $result = Test-TargetResource @testParameters + $result | Should Be $true - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - It 'Should return the same values as passed as parameters' { - $result.NodeName | Should Be $nodeName - $result.InstanceName | Should Be $instanceName - $result.Principal | Should Be $principal - } + It 'Should return that desired state is present when wanted desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - It 'Should return the permissions passed as parameter' { - foreach ($currentPermission in $permission) { - if( $result.Permission -ccontains $currentPermission ) { - $permissionState = $true - } else { - $permissionState = $false - break - } - } - - $permissionState | Should Be $true - } + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - It 'Should call the mock function Get-SQLPSInstance' { - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } - } - Assert-VerifiableMocks + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state' { - It 'Should return that desired state is absent when wanted desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + Assert-VerifiableMocks + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Context 'When the system is not in the desired state' { + It 'Should not throw error when desired state is to be Present' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - It 'Should return that desired state is absent when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { - It 'Should return that desired state is present when wanted desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + It 'Should not throw error when desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - $result = Test-TargetResource @testParameters - $result | Should Be $true + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $principal - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } - It 'Should return that desired state is present when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Context 'When the system is not in the desired state' { - It 'Should not throw error when desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + Context 'When the system is in the desired state' { + It 'Should not throw error when desired state is to be Present' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - { Set-TargetResource @testParameters } | Should Not Throw + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Present' } - It 'Should not throw error when desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - Context 'When the system is in the desired state' { - It 'Should not throw error when desired state is to be Present' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + It 'Should not throw error when desired state is to be Absent' { + Mock -CommandName Get-SQLPSInstance -MockWith { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - { Set-TargetResource @testParameters } | Should Not Throw + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$nodeName\$instanceName" + $mockObjectSmoServer.DisplayName = $instanceName + $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + return $mockObjectSmoServer + } -ModuleName $script:DSCResourceName -Verifiable + + $testParameters = $defaultParameters + $testParameters += @{ + Ensure = 'Absent' } - It 'Should not throw error when desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName - $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = 'Should not call Grant() or Revoke()' - - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - - { Set-TargetResource @testParameters } | Should Not Throw + { Set-TargetResource @testParameters } | Should Not Throw - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - #> - Assert-VerifiableMocks } +#> + Assert-VerifiableMocks } - finally - { - #region FOOTER +} +finally +{ + #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - } + #endregion } - -$testJob | Receive-Job -Wait diff --git a/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 index dee7d868c..451988992 100644 --- a/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 @@ -1,275 +1,263 @@ -<# - AppVeyor build worker loads the SMO assembly which unable the tests to load the SMO stub classes. - Running the tests in a Start-Job script block give us a clean environment. This is a workaround. -#> -$testJob = Start-Job -ArgumentList $PSScriptRoot -ScriptBlock { - param - ( - [System.String] $PSScriptRoot - ) - $script:DSCModuleName = 'xSQLServer' - $script:DSCResourceName = 'MSFT_xSQLServerRole' - - #region HEADER - - # Unit Test Template Version: 1.1.0 - [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 'DSCResource.Tests\TestHelper.psm1') -Force +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerRole' - $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit +#region HEADER - #endregion HEADER +# Unit Test Template Version: 1.1.0 +[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\')) +} - # Begin Testing - try - { - #region Pester Test Initialization +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Unit - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' +#endregion HEADER - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName - ServerRole = 'dbcreator' - } +# Begin Testing +try +{ + #region Pester Test Initialization - #endregion Pester Test Initialization + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Roles { - return @{ - 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable + $nodeName = 'localhost' + $instanceName = 'MSSQLSERVER' - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownUser' - } - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + $defaultParameters = @{ + SQLInstanceName = $instanceName + SQLServer = $nodeName + ServerRole = 'dbcreator' + } - $result = Get-TargetResource @testParameters + #endregion Pester Test Initialization - It 'Should return the state as absent' { - $result.Ensure | Should Be 'Absent' - } - - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + Describe "$($script:DSCResourceName)\Get-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Roles { + return @{ + 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable - It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Context 'When the system is not in the desired state' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownUser' } - - Context 'When the system is in the desired state for a loginName' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'CONTOSO\SQL-Admin' - Ensure = 'Present' - } - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - $result = Get-TargetResource @testParameters + $result = Get-TargetResource @testParameters - It 'Should return the state as present' { - $result.Ensure | Should Be 'Present' - } - - It 'Should return the same values as passed as parameters' { - $result.SQLServer | Should Be $testParameters.SQLServer - $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should return the state as absent' { + $result.Ensure | Should Be 'Absent' + } - It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name } - Assert-VerifiableMocks + It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } } + + Context 'When the system is in the desired state for a loginName' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'CONTOSO\SQL-Admin' + Ensure = 'Present' + } + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Roles { - return @{ - 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - It 'Should return the test as false when desired loginName does not exist' { - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'UnknownUser' - Ensure = 'Present' - } + $result = Get-TargetResource @testParameters - $result = Test-TargetResource @testParameters - $result | Should Be $false + It 'Should return the state as present' { + $result.Ensure | Should Be 'Present' + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should return the same values as passed as parameters' { + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Name | Should Be $testParameters.Name + } - It 'Should return the test as false when non-desired loginName exist' { + It 'Should call the mock function Connect-SQL and Confirm-SqlServerRoleMember' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + } + } - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + Assert-VerifiableMocks + } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'NonDesiredUser' - Ensure = 'Absent' + Describe "$($script:DSCResourceName)\Test-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Roles { + return @{ + 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state' { + It 'Should return the test as false when desired loginName does not exist' { - $result = Test-TargetResource @testParameters - $result | Should Be $false + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'UnknownUser' + Ensure = 'Present' } - } - Context 'When the system is in the desired state' { - It 'Should return the test as true when desired loginName exist' { + $result = Test-TargetResource @testParameters + $result | Should Be $false - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'CONTOSO\SQL-Admin' - Ensure = 'Present' - } + It 'Should return the test as false when non-desired loginName exist' { - $result = Test-TargetResource @testParameters - $result | Should Be $true + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'NonDesiredUser' + Ensure = 'Absent' } - - It 'Should return the test as true when non-desired loginName does not exist' { - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $false - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'CONTOSO\SQL-Admin' - Ensure = 'Absent' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } - - Assert-VerifiableMocks } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Roles { - return @{ - 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state - PRESENT' { + Context 'When the system is in the desired state' { + It 'Should return the test as true when desired loginName exist' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + $testParameters = $defaultParameters $testParameters += @{ Name = 'CONTOSO\SQL-Admin' Ensure = 'Present' } - It 'Should call the mock function Connect-SQL and Add-SqlServerRoleMember' { - - Mock -CommandName Add-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable - - Set-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Add-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - It 'Should return the state as present when desired loginName in server role successfully added' { - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - $result.Ensure | Should Be 'Present' - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It } + + It 'Should return the test as true when non-desired loginName does not exist' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable - Context 'When the system is not in the desired state - ABSENT' { $testParameters = $defaultParameters $testParameters += @{ - Name = 'NonDesiredLogin' + Name = 'CONTOSO\SQL-Admin' Ensure = 'Absent' } - It 'Should call the mock function Connect-SQL and Remove-SqlServerRoleMember' { + $result = Test-TargetResource @testParameters + $result | Should Be $true - Mock -CommandName Remove-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + } - Set-TargetResource @testParameters - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Remove-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-VerifiableMocks + } - It 'Should return the state as absent when desired loginName in server role successfully dropped' { - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + Describe "$($script:DSCResourceName)\Set-TargetResource" { + Mock -CommandName Connect-SQL -MockWith { + return New-Object Object | + Add-Member ScriptProperty Roles { + return @{ + 'dbcreator' = @( ( New-Object Microsoft.SqlServer.Management.Smo.ServerRole -ArgumentList @( $null, 'CONTOSO\SQL-Admin')) ) + } + } -PassThru -Force + } -ModuleName $script:DSCResourceName -Verifiable + + Context 'When the system is not in the desired state - PRESENT' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'CONTOSO\SQL-Admin' + Ensure = 'Present' + } - $result = Get-TargetResource @testParameters - $result.Ensure | Should Be 'absent' - } + It 'Should call the mock function Connect-SQL and Add-SqlServerRoleMember' { + + Mock -CommandName Add-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Add-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return the state as present when desired loginName in server role successfully added' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' } } - } - finally - { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Context 'When the system is not in the desired state - ABSENT' { + $testParameters = $defaultParameters + $testParameters += @{ + Name = 'NonDesiredLogin' + Ensure = 'Absent' + } + + It 'Should call the mock function Connect-SQL and Remove-SqlServerRoleMember' { - #endregion + Mock -CommandName Remove-SqlServerRoleMember -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + + Set-TargetResource @testParameters + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Remove-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + } + + It 'Should return the state as absent when desired loginName in server role successfully dropped' { + + Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $false } -ModuleName $script:DSCResourceName -Verifiable + + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'absent' + } + } } } +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment -$testJob | Receive-Job -Wait + #endregion +} diff --git a/Tests/Unit/MSFT_xSQLServerScript.Test.ps1 b/Tests/Unit/MSFT_xSQLServerScript.Test.ps1 deleted file mode 100644 index 66f132305..000000000 --- a/Tests/Unit/MSFT_xSQLServerScript.Test.ps1 +++ /dev/null @@ -1,212 +0,0 @@ -<# - .SYNOPSIS - Automated unit test for MSFT_xSQLServerScript DSC Resource -#> - - -$Script:DSCModuleName = 'MSFT_xSQLServerScript' -$Script:DSCResourceName = 'MSFT_xSQLServerScript' - -#region HEADER - -# Unit Test Template Version: 1.1.0 -[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 (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $script:DSCModuleName ` - -DSCResourceName $script:DSCResourceName ` - -TestType Unit - -#endregion HEADER - -Add-Type -ErrorAction SilentlyContinue -TypeDefinition @' -namespace Microsoft.SqlServer.Management.PowerShell -{ - public class SqlPowerShellSqlExecutionException : System.Exception - { - public SqlPowerShellSqlExecutionException() - { - } - } -} -'@ - -# Begin Testing -try -{ - #region Pester Test Initialization - Function script:Invoke-SqlCmd {} - #endregion Pester Test Initialization - - #region Test Sql script throws an error - Describe 'Test Sql script throws an error' { - Mock -CommandName Import-Module -MockWith {} - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method returns successfully' { - Mock -CommandName Invoke-SqlCmd -MockWith { "" } - - $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.GetType() | should be "hashtable" - } - - It 'Test method returns false' { - Mock -CommandName Invoke-SqlCmd -MockWith { throw New-Object Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException} - - Test-TargetResource @testParameters | should be $false - } - - It 'Set method calls Invoke-SqlCmd' { - Mock -CommandName Invoke-SqlCmd -MockWith { "" } -Verifiable - - $result = Set-TargetResource @testParameters - - Assert-MockCalled -CommandName Invoke-SqlCmd -Times 1 - } - } - #endregion Test Sql script throws an error - - #region Test Sql script returns null - Describe 'Test Sql script returns null' { - Mock -CommandName Import-Module -MockWith {} - Mock -CommandName Invoke-SqlCmd -MockWith { $null } - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method returns successfully' { - $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.GetType() | should be "hashtable" - } - - It 'Test method returns true' { - Test-TargetResource @testParameters | should be $true - } - } - #endregion Test Sql script returns null - - #region Get SQl script throws and error - Describe 'Get SQl script throws and error' { - Mock -CommandName Import-Module -MockWith {} - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get throws when get sql script throws' { - $throwMessage = "Failed to run SQL Script" - - Mock -CommandName Invoke-SqlCmd -MockWith { Throw $throwMessage } - - { Get-TargetResource @testParameters } | should throw $throwMessage - } - - It 'Test method returns true' { - Mock -CommandName Invoke-SqlCmd -MockWith { $null } - - Test-TargetResource @testParameters | should be $true - } - } - #endregion - - #region Set-TargetResource throws when Set Sql script throws - Describe 'Set-TargetResource throws when Set Sql script throws' { - Mock -CommandName Import-Module -MockWith {} - Mock -CommandName Invoke-SqlCmd -MockWith { $null } - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method returns successfully' { - $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.GetType() | should be "hashtable" - } - - It 'Test method returns true' { - Test-TargetResource @testParameters | should be $true - } - - It 'Set method throws' { - $throwMessage = "Failed to execute set Sql script" - - Mock -CommandName Invoke-SqlCmd -MockWith { throw $throwMessage } - - { Set-TargetResource @testParameters } | should throw $throwMessage - } - } - #endregion - - #region Failed to import SQLPS module - Describe 'Failed to import SQLPS module' { - $throwMessage = "Failed to import module SQLPS" - - Mock -CommandName Import-Module -MockWith { throw $throwMessage } - - $testParameters = @{ - ServerInstance = $env:COMPUTERNAME - SetFilePath = "set.sql" - GetFilePath = "get.sql" - TestFilePath = "test.sql" - } - - It 'Get method throws' { - { $result = Get-TargetResource @testParameters } | should throw $throwMessage - } - - It 'Test method throws' { - { Test-TargetResource @testParameters } | should throw $throwMessage - } - - It 'Set method throws' { - { Set-TargetResource @testParameters} | should throw $throwMessage - } - } - #endregion - -} -finally -{ - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion -} diff --git a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 new file mode 100644 index 000000000..03166829b --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 @@ -0,0 +1,246 @@ +<# + .SYNOPSIS + Automated unit test for MSFT_xSQLServerScript DSC Resource +#> + +# Suppression of this PSSA rule allowed in tests. +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +Param() + +#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 -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName 'xSQLServer' ` + -DSCResourceName 'MSFT_xSQLServerScript' ` + -TestType Unit + +#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') -Force +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope 'MSFT_xSQLServerScript' { + + $script:DSCModuleName = 'xSQLServer' + $resourceName = 'MSFT_xSQLServerScript' + $sqlServerHelperModuleName = 'xSQLServerHelper' + + $testParameters = @{ + ServerInstance = $env:COMPUTERNAME + SetFilePath = "set.sql" + GetFilePath = "get.sql" + TestFilePath = "test.sql" + } + + Describe "$resourceName\Get-TargetResource" { + + Context 'Get-TargetResource fails to import SQLPS module' { + $throwMessage = "Failed to import SQLPS module." + + Mock -CommandName Import-Module -MockWith { + throw $throwMessage + } -ModuleName $sqlServerHelperModuleName + + It 'Should throw the correct error from Import-Module' { + { Get-TargetResource @testParameters } | Should Throw $throwMessage + } + } + + Context 'Get-TargetResource returns script results successfully' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + 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 'Get-TargetResource throws an error when running the script in the GetFilePath parameter' { + $errorMessage = "Failed to run SQL Script" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { + 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-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + + It 'Should throw the correct error from Import-Module' { + { Set-TargetResource @testParameters } | Should Throw $throwMessage + } + } + + Context 'Set-TargetResource runs script without issue' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { + return '' + } + + It 'Should return the expected results' { + $result = Set-TargetResource @testParameters + $result | Should Be '' + } + } + + Context 'Set-TargetResource throws an error when running the script in the SetFilePath parameter' { + $errorMessage = "Failed to run SQL Script" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw $errorMessage + } + + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Set-TargetResource @testParameters } | Should Throw $errorMessage + } + } + } + + Describe "$resourceName\Test-TargetResource" { + Context 'Test-TargetResource fails to import SQLPS module' { + $throwMessage = 'Failed to import SQLPS module.' + + Mock -CommandName Import-Module -MockWith { + throw $throwMessage + } -ModuleName $sqlServerHelperModuleName + + It 'Should throw the correct error from Import-Module' { + { Set-TargetResource @testParameters } | Should Throw $throwMessage + } + } + + Context 'Test-TargetResource runs script without issue' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith {} + + It 'Should return true' { + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + } + + Context 'Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' { + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw New-Object Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException + } + + 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" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw $errorMessage + } + + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Test-TargetResource @testParameters } | Should Throw $errorMessage + } + } + } + + Describe "$resourceName\Invoke-SqlScript" { + $invokeScriptParameters = @{ + ServerInstance = $env:COMPUTERNAME + SqlScriptPath = "set.sql" + } + + Context 'Invoke-SqlScript fails to import SQLPS module' { + $throwMessage = "Failed to import SQLPS module." + + Mock -CommandName Import-Module -MockWith { + throw $throwMessage + } -ModuleName $sqlServerHelperModuleName + + It 'Should throw the correct error from Import-Module' { + { Invoke-SqlScript @invokeScriptParameters } | Should Throw $throwMessage + } + } + + Context 'Invoke-SQlScript is called with credentials' { + $passwordPlain = "password" + $user = "User" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith {} -ParameterFilter { + ($Username -eq $user) -and ($Password -eq $passwordPlain) + } + + $password = ConvertTo-SecureString -String $passwordPlain -AsPlainText -Force + $cred = New-Object pscredential -ArgumentList $user, $password + + It 'Should call Invoke-Sqlcmd with correct parameters' { + $invokeScriptParameters.Add("Credential", $cred) + $null = Invoke-SqlScript @invokeScriptParameters + + Assert-MockCalled -CommandName Invoke-Sqlcmd -ParameterFilter { + ($Username -eq $user) -and ($Password -eq $passwordPlain) + } -Times 1 -Exactly -Scope It + } + } + + Context 'Invoke-SqlScript fails to execute the SQL scripts' { + $errorMessage = "Failed to run SQL Script" + + Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Invoke-Sqlcmd -MockWith { + throw $errorMessage + } + + It 'Should throw the correct error from Invoke-Sqlcmd' { + { Invoke-SqlScript @invokeScriptParameters } | Should Throw $errorMessage + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index 6ba30fc31..d1f061a3d 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -76,6 +76,10 @@ try $mockDefaultInstance_IntegrationServiceName = $mockSqlIntegrationName $mockDefaultInstance_AnalysisServiceName = 'MSSQLServerOLAPService' + $mockDefaultInstance_FailoverClusterNetworkName = 'TestDefaultCluster' + $mockDefaultInstance_FailoverClusterIPAddress = '10.0.0.10' + $mockDefaultInstance_FailoverClusterGroupName = "SQL Server ($mockDefaultInstance_InstanceName)" + $mockNamedInstance_InstanceName = 'TEST' $mockNamedInstance_DatabaseServiceName = "$($mockSqlDatabaseEngineName)`$$($mockNamedInstance_InstanceName)" $mockNamedInstance_AgentServiceName = "$($mockSqlAgentName)`$$($mockNamedInstance_InstanceName)" @@ -84,17 +88,43 @@ try $mockNamedInstance_IntegrationServiceName = $mockSqlIntegrationName $mockNamedInstance_AnalysisServiceName = "$($mockSqlAnalysisName)`$$($mockNamedInstance_InstanceName)" + $mockNamedInstance_FailoverClusterNetworkName = 'TestDefaultCluster' + $mockNamedInstance_FailoverClusterIPAddress = '10.0.0.20' + $mockNamedInstance_FailoverClusterGroupName = "SQL Server ($mockNamedInstance_InstanceName)" + $mockmockSetupCredentialUserName = "COMPANY\sqladmin" + $mockmockSetupCredentialPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force $mockSetupCredential = New-Object System.Management.Automation.PSCredential( $mockmockSetupCredentialUserName, $mockmockSetupCredentialPassword ) $mockSqlServiceAccount = 'COMPANY\SqlAccount' $mockAgentServiceAccount = 'COMPANY\AgentAccount' - $mockSourceFolder = 'Source' # The parameter SourceFolder has a default value of 'Source', so lets mock that as well. + $mockClusterNodes = @($env:COMPUTERNAME,'SQL01','SQL02') + + $mockClusterDiskMap = @{ + UserData = 'K:' + UserLogs = 'L:' + TempDbData = 'M:' + TempDbLogs = 'N:' + SQLBackup = 'O:' + } + + $mockClusterSites = @( + @{ + Name = 'SiteA' + Address = '10.0.0.100' + Mask = '255.255.255.0' + }, + @{ + Name = 'SiteB' + Address = '10.0.10.100' + Mask = '255.255.255.0' + } + ) #region Function mocks - $mockGetSQLVersion = { + $mockGetSqlMajorVersion = { return $mockSqlMajorVersion } @@ -102,37 +132,103 @@ try return @() } - $mockGetWmiObject_SqlProduct = { + $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber = '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' + $mockSqlServerManagementStudio2012_ProductIdentifyingNumber = '{A7037EB2-F953-4B12-B843-195F4D988DA1}' + $mockSqlServerManagementStudio2014_ProductIdentifyingNumber = '{75A54138-3B98-4705-92E4-F619825B121F}' + $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber = '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' + $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber = '{7842C220-6E9A-4D5A-AE70-0E138271F883}' + $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber = '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' + + $mockRegistryUninstallProductsPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' + + $mockGetItemProperty_UninstallProducts2008R2 = { + return @( + $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber, # Mock product SSMS 2008 and SSMS 2008 R2 + $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber # Mock product ADV_SSMS 2012 + ) + } + + $mockGetItemProperty_UninstallProducts2012 = { + return @( + $mockSqlServerManagementStudio2012_ProductIdentifyingNumber, # Mock product ADV_SSMS 2008 and ADV_SSMS 2008 R2 + $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber # Mock product SSMS 2014 + ) + } + + $mockGetItemProperty_UninstallProducts2014 = { + return @( + $mockSqlServerManagementStudio2014_ProductIdentifyingNumber, # Mock product SSMS 2012 + $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber # Mock product ADV_SSMS 2014 + ) + } + + $mockGetItemProperty_UninstallProducts = { + return @( + $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber, # Mock product SSMS 2008 and SSMS 2008 R2 + $mockSqlServerManagementStudio2012_ProductIdentifyingNumber, # Mock product ADV_SSMS 2008 and ADV_SSMS 2008 R2 + $mockSqlServerManagementStudio2014_ProductIdentifyingNumber, # Mock product SSMS 2012 + $mockSqlServerManagementStudioAdvanced2008R2_ProductIdentifyingNumber, # Mock product ADV_SSMS 2012 + $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber, # Mock product SSMS 2014 + $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber # Mock product ADV_SSMS 2014 + ) + } + + $mockGetCimInstance_DefaultInstance_DatabaseService = { return @( ( - # Mock product SSMS 2008 and SSMS 2008 R2 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{72AB7E6F-BC24-481E-8C45-1AB5B3DD795D}' -PassThru -Force - ), + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_DatabaseServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_AgentService = { + return @( ( - # Mock product ADV_SSMS 2008 and SSMS 2008 R2 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{B5FE23CC-0151-4595-84C3-F1DE6F44FE9B}' -PassThru -Force - ), + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AgentServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_FullTextService = { + return @( ( - # Mock product SSMS 2012 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{A7037EB2-F953-4B12-B843-195F4D988DA1}' -PassThru -Force - ), + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FullTextServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_ReportingService = { + return @( ( - # Mock product ADV_SSMS 2012 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{7842C220-6E9A-4D5A-AE70-0E138271F883}' -PassThru -Force - ), + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_ReportingServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_IntegrationService = { + return @( ( - # Mock product SSMS 2014 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{75A54138-3B98-4705-92E4-F619825B121F}' -PassThru -Force - ), + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_DefaultInstance_AnalysisService = { + return @( ( - # Mock product ADV_SSMS 2014 - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'IdentifyingNumber' -Value '{B5ECFA5C-AC4F-45A4-A12E-A76ABDD9CCBA}' -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AnalysisServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) ) } @@ -140,67 +236,127 @@ try $mockGetService_DefaultInstance = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_DatabaseServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AgentServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FullTextServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_ReportingServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockDefaultInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_AnalysisServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) ) } + $mockGetCimInstance_NamedInstance_DatabaseService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_DatabaseServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_AgentService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AgentServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_FullTextService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_FullTextServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_ReportingService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_ReportingServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_IntegrationService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + + $mockGetCimInstance_NamedInstance_AnalysisService = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AnalysisServiceName -PassThru | + Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force + ) + ) + } + $mockGetService_NamedInstance = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_DatabaseServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AgentServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockAgentServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_FullTextServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_ReportingServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value ($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion) -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ), ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockNamedInstance_AnalysisServiceName -PassThru | Add-Member -MemberType NoteProperty -Name 'StartName' -Value $mockSqlServiceAccount -PassThru -Force ) @@ -210,7 +366,7 @@ try $mockGetItemProperty_ConfigurationState = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'SQL_Replication_Core_Inst' -Value 1 -PassThru -Force ) ) @@ -219,12 +375,12 @@ try $mockGetItemProperty_SQL = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name $mockDefaultInstance_InstanceName -Value $mockDefaultInstance_InstanceId -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockDefaultInstance_InstanceName -Value $mockDefaultInstance_InstanceId -PassThru -Force ), ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name $mockNamedInstance_InstanceName -Value $mockNamedInstance_InstanceId -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockNamedInstance_InstanceName -Value $mockNamedInstance_InstanceId -PassThru -Force ) ) } @@ -232,8 +388,8 @@ try $mockGetItemProperty_SharedDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedDirectory -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedDirectory -PassThru -Force ) ) } @@ -241,8 +397,8 @@ try $mockGetItem_SharedDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force ) ) } @@ -250,8 +406,8 @@ try $mockGetItemProperty_SharedWowDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedWowDirectory -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name '28A1158CDF9ED6B41B2B7358982D4BA8' -Value $mockSqlSharedWowDirectory -PassThru -Force ) ) } @@ -259,8 +415,8 @@ try $mockGetItem_SharedWowDirectory = { return @( ( - New-Object Object | - Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Property' -Value '28A1158CDF9ED6B41B2B7358982D4BA8' -PassThru -Force ) ) } @@ -268,7 +424,7 @@ try $mockGetItemProperty_Setup = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'SqlProgramDir' -Value $mockSqlProgramDirectory -PassThru -Force ) ) @@ -277,13 +433,37 @@ try $mockGetItemProperty_ServicesAnalysis = { return @( ( - New-Object Object | + New-Object Object | Add-Member -MemberType NoteProperty -Name 'ImagePath' -Value ('"C:\Program Files\Microsoft SQL Server\OLAP\bin\msmdsrv.exe" -s "{0}"' -f $mockSqlAnalysisConfigDirectory) -PassThru -Force ) ) } $mockConnectSQL = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'LoginMode' -Value $mockSqlLoginMode -PassThru | + Add-Member -MemberType NoteProperty -Name 'Collation' -Value $mockSqlCollation -PassThru | + Add-Member -MemberType NoteProperty -Name 'InstallDataDirectory' -Value $mockSqlInstallPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'BackupDirectory' -Value $mockSqlBackupPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'SQLTempDBDir' -Value $mockSqlTempDatabasePath -PassThru | + Add-Member -MemberType NoteProperty -Name 'SQLTempDBLogDir' -Value $mockSqlTempDatabaseLogPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'DefaultFile' -Value $mockSqlDefaultDatabaseFilePath -PassThru | + Add-Member -MemberType NoteProperty -Name 'DefaultLog' -Value $mockSqlDefaultDatabaseLogPath -PassThru | + Add-Member ScriptProperty Logins { + return @( ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockSqlSystemAdministrator -PassThru | + Add-Member ScriptMethod ListMembers { + return @('sysadmin') + } -PassThru -Force + ) ) + } -PassThru -Force + ) + ) + } + + $mockConnectSQLCluster = { return @( ( New-Object Object | @@ -295,6 +475,7 @@ try Add-Member -MemberType NoteProperty -Name 'SQLTempDBLogDir' -Value $mockSqlTempDatabaseLogPath -PassThru | Add-Member -MemberType NoteProperty -Name 'DefaultFile' -Value $mockSqlDefaultDatabaseFilePath -PassThru | Add-Member -MemberType NoteProperty -Name 'DefaultLog' -Value $mockSqlDefaultDatabaseLogPath -PassThru | + Add-Member -MemberType NoteProperty -Name 'IsClustered' -Value $true -PassThru | Add-Member ScriptProperty Logins { return @( ( New-Object Object | Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockSqlSystemAdministrator -PassThru | @@ -310,7 +491,7 @@ try $mockConnectSQLAnalysis = { return @( ( - New-Object Object | + New-Object Object | Add-Member ScriptProperty ServerProperties { return @{ 'CollationName' = @( New-Object Object | Add-Member NoteProperty -Name 'Value' -Value $mockSqlAnalysisCollation -PassThru -Force ) @@ -319,7 +500,7 @@ try 'LogDir' = @( New-Object Object | Add-Member NoteProperty -Name 'Value' -Value $mockSqlAnalysisLogDirectory -PassThru -Force ) 'BackupDir' = @( New-Object Object | Add-Member NoteProperty -Name 'Value' -Value $mockSqlAnalysisBackupDirectory -PassThru -Force ) } - } -PassThru | + } -PassThru | Add-Member ScriptProperty Roles { return @{ 'Administrators' = @( New-Object Object | @@ -335,27 +516,226 @@ try ) } + $mockRobocopyExecutableName = 'Robocopy.exe' + $mockRobocopyExectuableVersionWithoutUnbufferedIO = '6.2.9200.00000' + $mockRobocopyExectuableVersionWithUnbufferedIO = '6.3.9600.16384' + $mockRobocopyExectuableVersion = '' # Set dynamically during runtime + $mockRobocopyArgumentSilent = '/njh /njs /ndl /nc /ns /nfl' + $mockRobocopyArgumentCopySubDirectoriesIncludingEmpty = '/e' + $mockRobocopyArgumentDeletesDestinationFilesAndDirectoriesNotExistAtSource = '/purge' + $mockRobocopyArgumentUseUnbufferedIO = '/J' + $mockRobocopyArgumentSourcePath = 'C:\Source\SQL2016' + $mockRobocopyArgumentDestinationPath = 'D:\Temp' + + $mockGetCommand = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockRobocopyExecutableName -PassThru | + Add-Member ScriptProperty FileVersionInfo { + return @( ( New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ProductVersion' -Value $mockRobocopyExectuableVersion -PassThru -Force + ) ) + } -PassThru -Force + ) + ) + } + + $mockStartProcessExpectedArgument = '' # Set dynamically during runtime + $mockStartProcessExitCode = 0 # Set dynamically during runtime + + $mockStartProcess = { + if ( $ArgumentList -cne $mockStartProcessExpectedArgument ) + { + throw "Expected arguments was not the same as the arguments in the function call.`nExpected: '$mockStartProcessExpectedArgument' `n But was: '$ArgumentList'" + } + + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value 0 -PassThru -Force + } + + $mockStartProcess_WithExitCode = { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ExitCode' -Value $mockStartProcessExitCode -PassThru -Force + } + + $mockSourcePathUNCWithoutLeaf = '\\server\share' + $mockSourcePathGuid = 'cc719562-0f46-4a16-8605-9f8a47c70402' + $mockNewGuid = { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Guid' -Value $mockSourcePathGuid -PassThru -Force + } + $mockGetTemporaryFolder = { return $mockSourcePathUNC } + $mockGetCimInstance_MSClusterResource = { + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource','root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server ($mockCurrentInstanceName)" -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server' -TypeName 'String' -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'PrivateProperties' -Value @{ InstanceName = $mockCurrentInstanceName } -PassThru -Force + ) + ) + } + + $mockGetCimInstance_MSClusterResourceGroup_AvailableStorage = { + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_ResourceGroup', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value 'Available Storage' -PassThru -Force + ) + ) + } + + $mockGetCimInstance_MSClusterNetwork = { + return @( + ( + $mockClusterSites | ForEach-Object { + $network = $_ + + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Network', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value "$($network.Name)_Prod" -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Role' -Value 2 -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Address' -Value $network.Address -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'AddressMask' -Value $network.Mask -PassThru -Force + } + ) + ) + } + + $mockGetCimAssociatedInstance_MSClusterResourceGroup_DefaultInstance = { + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_ResourceGroup', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDefaultInstance_FailoverClusterGroupName -PassThru -Force + ) + ) + } + + $mockGetCimAssociatedInstance_MSClusterResource_DefaultInstance = { + return @( + ( + @('Network Name','IP Address') | ForEach-Object { + $resourceType = $_ + + $propertyValue = @{ + MemberType = 'NoteProperty' + Name = 'PrivateProperties' + Value = $null + } + + switch ($resourceType) + { + 'Network Name' + { + $propertyValue.Value = @{ DnsName = $mockDefaultInstance_FailoverClusterNetworkName } + } + + 'IP Address' + { + $propertyValue.Value = @{ Address = $mockDefaultInstance_FailoverClusterIPAddress } + } + } + + return New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Type' -Value $resourceType -PassThru -Force | + Add-Member @propertyValue -PassThru -Force + } + ) + ) + } + + # Mock to return physical disks that are part of the "Available Storage" cluster role + $mockGetCimAssociatedInstance_MSCluster_ResourceGroupToResource = { + return @( + ( + $mockClusterDiskMap.Keys | ForEach-Object { + $diskName = $_ + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource','root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $diskName -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'State' -Value 2 -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'Type' -Value 'Physical Disk' -PassThru -Force + } + ) + ) + } + + $mockGetCimAssociatedInstance_MSCluster_ResourceToPossibleOwner = { + return @( + ( + $mockClusterNodes | ForEach-Object { + $node = $_ + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Node', 'root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $node -PassThru -Force + } + ) + ) + } + + $mockGetCimAssociatedInstance_MSCluster_DiskPartition = { + $clusterDiskName = $InputObject.Name + $clusterDiskPath = $mockClusterDiskMap.$clusterDiskName + + return @( + ( + New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_DiskPartition','root/MSCluster' | + Add-Member -MemberType NoteProperty -Name 'Path' -Value $clusterDiskPath -PassThru -Force + ) + ) + } + <# - Needed a way to see into the Set-method for the arguments the Set-method is building and sending to 'setup.exe', and fail - the test if the arguments is diffentent from the expected arguments. + Needed a way to see into the Set-method for the arguments the Set-method is building and sending to 'setup.exe', and fail + the test if the arguments is different from the expected arguments. Solved this by dynamically set the expected arguments before each It-block. If the arguments differs the mock of StartWin32Process throws an error message, similiar to what Pester would have reported (expected -> but was). - #> - $mockStartWin32ProcessExpectedArgument = '' + #> + $mockStartWin32ProcessExpectedArgument = @{} + + $mockStartWin32ProcessExpectedArgumentClusterDefault = @{ + IAcceptSQLServerLicenseTerms = 'True' + Quiet = 'True' + InstanceName = 'MSSQLSERVER' + AGTSVCSTARTUPTYPE = 'Automatic' + Features = 'SQLENGINE' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + FailoverClusterGroup = 'SQL Server (MSSQLSERVER)' + } + $mockStartWin32Process = { - if ( $Arguments -ne $mockStartWin32ProcessExpectedArgument ) + $argumentHashTable = @{} + + # Break the argument string into a hash table + ($Arguments -split ' ?/') | ForEach-Object { + if ($_ -imatch '(\w+)="?([^/]+)"?') + { + $key = $Matches[1] + $value = ($Matches[2] -replace '" "','; ') -replace '"','' + $null = $argumentHashTable.Add($key, $value) + } + } + + # Start by checking whether we have the same number of parameters + New-VerboseMessage 'Verifying argument count (expected vs actual)' + $mockStartWin32ProcessExpectedArgument.Keys.Count | Should BeExactly $argumentHashTable.Keys.Count + + foreach ($argumentKey in $mockStartWin32ProcessExpectedArgument.Keys) { - throw "Expected arguments was not the same as the arguments in the function call.`nExpected: '$mockStartWin32ProcessExpectedArgument' `n But was: '$Arguments'" + New-VerboseMessage "Testing Parameter [$argumentKey]" + $argumentPassed = $argumentHashTable.ContainsKey($argumentKey) + $argumentPassed | Should Be $true + + $argumentValue = $argumentHashTable.$argumentKey + $argumentValue | Should Be $mockStartWin32ProcessExpectedArgument.$argumentKey } - return 'Process started' + return 'Process Started' } #endregion Function mocks - + # Default parameters that are used for the It-blocks $mockDefaultParameters = @{ SetupCredential = $mockSetupCredential @@ -363,63 +743,70 @@ try Features = 'SQLEngine,Replication,FullText,Rs,Is,As' } - Describe "xSQLServerSetup\Get-TargetResource" -Tag 'Get' { - #region Setting up TestDrive:\ + $mockDefaultClusterParameters = @{ + SetupCredential = $mockSetupCredential + + # Feature support is tested elsewhere, so just include the minimum + Features = 'SQLEngine' + + # Ensure we use "clustered" disks for our paths + SQLUserDBDir = 'K:\MSSQL\Data\' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDbDir = 'M:\MSSQL\TempDb\Data\' + SQLTempDbLogDir = 'N:\MSSQL\TempDb\Logs' + } + + Describe "xSQLServerSetup\Get-TargetResource" -Tag 'Get' { + #region Setting up TestDrive:\ # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName - $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder - + # 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) - $mockSqlMediaPathUNC = Join-Path -Path $mockSourcePathUNC -ChildPath $mockSourceFolder - # Mocking folder structure and mocking setup.exe - New-Item -Path $mockSqlMediaPath -ItemType Directory - Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' - #endregion Setting up TestDrive:\ BeforeEach { # General mocks - Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable + Mock -CommandName Get-SqlMajorVersion -MockWith $mockGetSqlMajorVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -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:\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 { + 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 + ) -and + $Name -eq 'ImagePath' + } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable + } -MockWith $mockGetItem_SharedDirectory -Verifiable # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable + } -MockWith $mockGetItem_SharedWowDirectory -Verifiable } $testProductVersion | ForEach-Object -Process { @@ -443,44 +830,68 @@ try SourceCredential = $null SourcePath = $mockSourcePath } - + if ($mockSqlMajorVersion -eq 13) { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -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 $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + 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 NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -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\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - - Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -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 -Exactly -Times 0 -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -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 + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It } It 'Should not return any names of installed features' { @@ -491,7 +902,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should BeNullOrEmpty $result.InstallSharedDir | Should BeNullOrEmpty @@ -528,44 +938,68 @@ try SourceCredential = $mockSetupCredential SourcePath = $mockSourcePathUNC } - + if ($mockSqlMajorVersion -eq 13) { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -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 $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + 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 NetUse -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -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_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - - Assert-MockCalled -CommandName NetUse -Exactly -Times 2 -Scope It + + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -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 -Exactly -Times 0 -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -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 + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It } It 'Should not return any names of installed features' { @@ -576,7 +1010,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePathUNC - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should BeNullOrEmpty $result.InstallSharedDir | Should BeNullOrEmpty @@ -614,40 +1047,145 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + if ($mockSqlMajorVersion -eq 10) { + 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 + } + + if ($mockSqlMajorVersion -eq 11) { + 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 NetUse -Verifiable + if ($mockSqlMajorVersion -eq 12) { + 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 New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_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_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - - Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -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 - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + if ($mockSqlMajorVersion -eq 13) { + 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 0 -Scope It + } else { + 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 2 -Scope It + } - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -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 correct names of installed features' { @@ -662,7 +1200,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should Be $mockDefaultInstance_InstanceName $result.InstallSharedDir | Should Be $mockSqlSharedDirectory @@ -700,40 +1237,145 @@ try SourcePath = $mockSourcePathUNC } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + if ($mockSqlMajorVersion -eq 10) { + 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 NetUse -Verifiable + if ($mockSqlMajorVersion -eq 11) { + 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 + } + + if ($mockSqlMajorVersion -eq 12) { + 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 New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_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_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - - Assert-MockCalled -CommandName NetUse -Exactly -Times 2 -Scope It + + Assert-MockCalled -CommandName New-SmbMapping -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Remove-SmbMapping -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 - Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 6 -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + if ($mockSqlMajorVersion -eq 13) { + 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 0 -Scope It + } else { + 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 2 -Scope It + } - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -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 correct names of installed features' { @@ -748,7 +1390,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePathUNC - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockDefaultInstance_InstanceName $result.InstanceID | Should Be $mockDefaultInstance_InstanceName $result.InstallSharedDir | Should Be $mockSqlSharedDirectory @@ -784,7 +1425,7 @@ try $mockSqlTempDatabaseLogPath = '' $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockNamedInstance_InstanceId)\MSSQL\DATA\" $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockNamedInstance_InstanceId)\MSSQL\DATA\" - + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for named instance" { BeforeEach { $testParameters = $mockDefaultParameters.Clone() @@ -795,43 +1436,63 @@ try SourcePath = $mockSourcePath } - # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 if ($mockSqlMajorVersion -eq 13) { # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -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 $mockGetItemProperty_UninstallProducts -Verifiable } else { - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + 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\$mockNamedInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + 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 -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -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 + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It } It 'Should not return any names of installed features' { @@ -842,7 +1503,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockNamedInstance_InstanceName $result.InstanceID | Should BeNullOrEmpty $result.InstallSharedDir | Should BeNullOrEmpty @@ -880,40 +1540,141 @@ try SourcePath = $mockSourcePath } - # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -Verifiable + if ($mockSqlMajorVersion -eq 10) { + 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 + } + + if ($mockSqlMajorVersion -eq 11) { + 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 + } + + if ($mockSqlMajorVersion -eq 12) { + 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_NamedInstance -Verifiable - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockGetService_NamedInstance -Verifiable + #region Mock Get-CimInstance + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_DatabaseServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_DatabaseService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AgentServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_AgentService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_FullTextServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_FullTextService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_ReportingServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_ReportingService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -MockWith $mockGetCimInstance_NamedInstance_IntegrationService -Verifiable + + Mock -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AnalysisServiceName'" + } -MockWith $mockGetCimInstance_NamedInstance_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\$mockNamedInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return the same values as passed as parameters' { $result = Get-TargetResource @testParameters $result.InstanceName | Should Be $testParameters.InstanceName - + 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 -Exactly -Times 6 -Scope It + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' + } -Exactly -Times 1 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + if ($mockSqlMajorVersion -eq 13) { + 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 0 -Scope It + } else { + 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 2 -Scope It + } - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + #region Assert Get-CimInstance + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_DatabaseServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AgentServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_FullTextServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_ReportingServiceName'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$(($mockNamedInstance_IntegrationServiceName -f $mockSqlMajorVersion))'" + } -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + $ClassName -eq 'Win32_Service' -and + $Filter -eq "Name = '$mockNamedInstance_AnalysisServiceName'" + } -Exactly -Times 1 -Scope It + #endregion Assert Get-CimInstance } It 'Should return correct names of installed features' { @@ -928,7 +1689,6 @@ try It 'Should return the correct values in the hash table' { $result = Get-TargetResource @testParameters $result.SourcePath | Should Be $mockSourcePath - $result.SourceFolder | Should Be $mockSourceFolder $result.InstanceName | Should Be $mockNamedInstance_InstanceName $result.InstanceID | Should Be $mockNamedInstance_InstanceName $result.InstallSharedDir | Should Be $mockSqlSharedDirectory @@ -955,71 +1715,149 @@ try $result.ISSvcAccountUsername | Should Be $mockSqlServiceAccount } } + + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a clustered default instance" { + + BeforeAll { + $testParams = $mockDefaultParameters.Clone() + $testParams.Remove('Features') + $testParams += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + Mock -CommandName Get-CimInstance -MockWith {} -Verifiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -Verifiable + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_Setup -Verifiable + + Mock -CommandName Get-Service -MockWith $mockEmptyHashtable -Verifiable + } + + It 'Should not attempt to collect cluster information for a standalone instance' { + + $currentState = Get-TargetResource @testParams + + Assert-MockCalled -CommandName Connect-SQL -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Exactly -Times 0 -Scope It + + $currentState.FailoverClusterGroupName | Should BeNullOrEmpty + $currentState.FailoverClusterNetworkName | Should BeNullOrEmpty + $currentState.FailoverClusterIPAddress | Should BeNullOrEmpty + } + } + + Context "When SQL Server version is $mockSqlMajorVersion and the system is in the desired state for a clustered default instance" { + + BeforeEach { + $testParams = $mockDefaultParameters.Clone() + $testParams.Remove('Features') + $testParams += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + + Mock -CommandName Connect-SQL -MockWith $mockConnectSQLCluster -Verifiable + + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResource -Verifiable -ParameterFilter { + $Filter -eq "Type = 'SQL Server'" + } + + 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' } + + Mock -CommandName Get-ItemProperty -MockWith $mockGetItemProperty_Setup -Verifiable + + Mock -CommandName Get-Service -MockWith $mockGetService_DefaultInstance -Verifiable + } + + It 'Should collect information for a clustered instance' { + $currentState = Get-TargetResource @testParams + + 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 1 -Scope It -ParameterFilter { $ResultClassName -eq 'MSCluster_ResourceGroup' } + Assert-MockCalled -CommandName Get-CimAssociatedInstance -Exactly -Times 2 -Scope It -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } + + $currentState.InstanceName | Should Be $testParams.InstanceName + } + + It 'Should return correct cluster information' { + $currentState = Get-TargetResource @testParams + + $currentState.FailoverClusterGroupName | Should Be $mockDefaultInstance_FailoverClusterGroupName + $currentState.FailoverClusterIPAddress | Should Be $mockDefaultInstance_FailoverClusterIPAddress + $currentSTate.FailoverClusterNetworkName | Should Be $mockDefaultInstance_FailoverClusterNetworkName + } + } } Assert-VerifiableMocks } - + Describe "xSQLServerSetup\Test-TargetResource" -Tag 'Test' { #region Setting up TestDrive:\ # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName - $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder - + # 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) - $mockSqlMediaPathUNC = Join-Path -Path $mockSourcePathUNC -ChildPath $mockSourceFolder - # Mocking folder structure and mocking setup.exe - New-Item -Path $mockSqlMediaPath -ItemType Directory - Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' - #endregion Setting up TestDrive:\ BeforeEach { # General mocks - Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable + Mock -CommandName Get-SqlMajorVersion -MockWith $mockGetSqlMajorVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -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:\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 { + 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 + ) -and + $Name -eq 'ImagePath' + } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable + } -MockWith $mockGetItem_SharedDirectory -Verifiable # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable + } -MockWith $mockGetItem_SharedWowDirectory -Verifiable } - # For this thest we only need to test one SQL Server version + # For this test we only need to test one SQL Server version $mockSqlMajorVersion = 13 $mockDefaultInstance_InstanceId = "$($mockSqlDatabaseEngineName)$($mockSqlMajorVersion).$($mockDefaultInstance_InstanceName)" @@ -1031,7 +1869,7 @@ try $mockSqlDefaultDatabaseFilePath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" $mockSqlDefaultDatabaseLogPath = "C:\Program Files\Microsoft SQL Server\$($mockDefaultInstance_InstanceId)\MSSQL\DATA\" - Context "When the system is not in the desired state" { + Context 'When the system is not in the desired state' { BeforeEach { $testParameters = $mockDefaultParameters $testParameters += @{ @@ -1041,89 +1879,265 @@ try } # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -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 $mockGetItemProperty_UninstallProducts -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -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-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable $result = Test-TargetResource @testParameters - $result| Should Be $false - + $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 -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -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 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 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_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 - + $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 -Exactly -Times 6 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -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 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_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 - + $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 -Exactly -Times 6 -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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + It 'Should return that the desired state is absent when a clustered instance cannot be found' { + $testClusterParameters = $testParameters.Clone() - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + $testClusterParameters += @{ + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + + $result = Test-TargetResource @testClusterParameters + + $result | Should Be $false } } @@ -1136,42 +2150,157 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockGetWmiObject_SqlProduct -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 - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_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_ConfigurationState -Verifiable + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" + } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + } -MockWith $mockGetItemProperty_Setup -Verifiable } It 'Should return that the desired state is present' { $result = Test-TargetResource @testParameters - $result| Should Be $true - + $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 -Exactly -Times 6 -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 present when the correct clustered instance was found' { + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 6 -Scope It + Mock -CommandName Connect-SQL -MockWith $mockConnectSQLCluster -Verifiable - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResource -Verifiable -ParameterFilter { + $Filter -eq "Type = 'SQL Server'" + } + + 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() + + $testClusterParameters += @{ + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + + $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 } } - + Assert-VerifiableMocks } @@ -1180,58 +2309,85 @@ try # Local path to TestDrive:\ $mockSourcePath = $TestDrive.FullName - $mockSqlMediaPath = Join-Path -Path $mockSourcePath -ChildPath $mockSourceFolder - + # 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) - $mockSqlMediaPathUNC = Join-Path -Path $mockSourcePathUNC -ChildPath $mockSourceFolder - # Mocking folder structure and mocking setup.exe - New-Item -Path $mockSqlMediaPath -ItemType Directory - Set-Content (Join-Path -Path $mockSqlMediaPath -ChildPath 'setup.exe') -Value 'Mock exe file' - + <# + Mocking folder structure. This takes the leaf from the $mockSourcePath (the Guid in this case) and + uses that to create a mock folder to mimic the temp folder that would be created in, for example, + temp folder 'C:\Windows\Temp'. + This folder is used when SourcePath is called with a leaf, for example '\\server\share\folder'. + #> + $mediaTempSourcePathWithLeaf = (Join-Path -Path $mockSourcePath -ChildPath (Split-Path -Path $mockSourcePath -Leaf)) + if (-not (Test-Path $mediaTempSourcePathWithLeaf)) + { + New-Item -Path $mediaTempSourcePathWithLeaf -ItemType Directory + } + + <# + Mocking folder structure. This takes the leaf from the New-Guid mock and uses that + to create a mock folder to mimic the temp folder that would be created in, for example, + temp folder 'C:\Windows\Temp'. + This folder is used when SourcePath is called without a leaf, for example '\\server\share'. + #> + $mediaTempSourcePathWithoutLeaf = (Join-Path -Path $mockSourcePath -ChildPath $mockSourcePathGuid) + if (-not (Test-Path $mediaTempSourcePathWithoutLeaf)) + { + New-Item -Path $mediaTempSourcePathWithoutLeaf -ItemType Directory + } + + # Mocking executable setup.exe which will be used for tests without parameter SourceCredential + Set-Content (Join-Path -Path $mockSourcePath -ChildPath 'setup.exe') -Value 'Mock exe file' + + # Mocking executable setup.exe which will be used for tests with parameter SourceCredential and an UNC path with leaf + Set-Content (Join-Path -Path $mediaTempSourcePathWithLeaf -ChildPath 'setup.exe') -Value 'Mock exe file' + + # Mocking executable setup.exe which will be used for tests with parameter SourceCredential and an UNC path without leaf + Set-Content (Join-Path -Path $mediaTempSourcePathWithoutLeaf -ChildPath 'setup.exe') -Value 'Mock exe file' + #endregion Setting up TestDrive:\ BeforeEach { # General mocks - Mock -CommandName GetSQLVersion -MockWith $mockGetSQLVersion -Verifiable + Mock -CommandName Get-SqlMajorVersion -MockWith $mockGetSqlMajorVersion -Verifiable Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable Mock -CommandName Connect-SQLAnalysis -MockWith $mockConnectSQLAnalysis -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:\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 { + 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 + ) -and + $Name -eq 'ImagePath' + } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable + } -MockWith $mockGetItem_SharedDirectory -Verifiable # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable + } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - Mock -CommandName Get-Item -ParameterFilter { + Mock -CommandName Get-Item -ParameterFilter { $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable + } -MockWith $mockGetItem_SharedWowDirectory -Verifiable Mock -CommandName StartWin32Process -MockWith $mockStartWin32Process -Verifiable Mock -CommandName WaitForWin32ProcessEnd -Verifiable @@ -1259,59 +2415,65 @@ try InstanceName = $mockDefaultInstance_InstanceName SourceCredential = $null SourcePath = $mockSourcePath + ProductKey = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' } - Mock -CommandName NetUse -Verifiable - Mock -CommandName Copy-ItemWithRoboCopy -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Start-Process -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-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable + $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\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/AGTSVCSTARTUPTYPE=Automatic', - '/InstanceName="MSSQLSERVER"', - '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', - '/SQLSysAdminAccounts="COMPANY\sqladmin"', - '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AGTSVCSTARTUPTYPE = 'Automatic' + InstanceName = 'MSSQLSERVER' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' + } { Set-TargetResource @testParameters } | Should Not Throw - - Assert-MockCalled -CommandName NetUse -Exactly -Times 0 -Scope It + + 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 Copy-ItemWithRoboCopy -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName New-Guid -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Start-Process -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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -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 StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1321,14 +2483,14 @@ try if( $mockSqlMajorVersion -eq 13 ) { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } It 'Should throw when feature parameter contains ''ADV_SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -1336,28 +2498,34 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'SSMS' + PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' + } { 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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -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 StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1367,28 +2535,35 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="ADV_SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'ADV_SSMS' + PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' + } { 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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -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 Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1397,7 +2572,7 @@ try } } - Context "When using SourceCredential parameter and SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { + Context "When using SourceCredential parameter, and using a UNC path with a leaf, and SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { BeforeEach { $testParameters = $mockDefaultParameters $testParameters += @{ @@ -1406,57 +2581,62 @@ try SourcePath = $mockSourcePathUNC } - Mock -CommandName NetUse -Verifiable - Mock -CommandName Copy-ItemWithRoboCopy -Verifiable + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Start-Process -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-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' - } -MockWith $mockEmptyHashtable -Verifiable - + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft copSQL Server\$mockDefaultInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_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 + $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 } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/AGTSVCSTARTUPTYPE=Automatic', - '/InstanceName="MSSQLSERVER"', - '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', - '/SQLSysAdminAccounts="COMPANY\sqladmin"', - '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AgtSvcStartupType = 'Automatic' + InstanceName = 'MSSQLSERVER' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + } { Set-TargetResource @testParameters } | Should Not Throw - - Assert-MockCalled -CommandName NetUse -Exactly -Times 4 -Scope It + + 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 Copy-ItemWithRoboCopy -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName New-Guid -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Start-Process -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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -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 Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1481,28 +2661,34 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'SSMS' + } { 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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -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 Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1512,27 +2698,191 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="MSSQLSERVER"', - '/Features="ADV_SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'ADV_SSMS' + } { 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 StartWin32Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It + } + } + } + + Context "When using SourceCredential parameter, and using a UNC path without a leaf, and SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { + BeforeEach { + $testParameters = $mockDefaultParameters + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $mockSetupCredential + SourcePath = $mockSourcePathUNCWithoutLeaf + } + + Mock -CommandName New-SmbMapping -Verifiable + Mock -CommandName Remove-SmbMapping -Verifiable + Mock -CommandName Start-Process -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 + } + + It 'Should set the system in the desired state when feature is SQLENGINE' { + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AGTSVCSTARTUPTYPE = 'Automatic' + InstanceName = 'MSSQLSERVER' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + } + + { 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 Start-Process -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 StartWin32Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It + } + + if( $mockSqlMajorVersion -eq 13 ) { + It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { + $testParameters.Features = 'SSMS' + $mockStartWin32ProcessExpectedArgument = @{} + + { Set-TargetResource @testParameters } | Should Throw "'SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." + } + + It 'Should throw when feature parameter contains ''ADV_SSMS'' when installing SQL Server 2016' { + $testParameters.Features = 'ADV_SSMS' + $mockStartWin32ProcessExpectedArgument = @{} + + { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." + } + } else { + It 'Should set the system in the desired state when feature is SSMS' { + $testParameters.Features = 'SSMS' + + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'SSMS' + } + + { 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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -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 Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It + } + + It 'Should set the system in the desired state when feature is ADV_SSMS' { + $testParameters.Features = 'ADV_SSMS' + + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'MSSQLSERVER' + Features = 'ADV_SSMS' + } + + { 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 StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1559,51 +2909,51 @@ try SourcePath = $mockSourcePath } - Mock -CommandName Get-WmiObject -ParameterFilter { - $Class -eq 'Win32_Product' + 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-WmiObject -ParameterFilter { - $Class -eq 'Win32_Service' - } -MockWith $mockEmptyHashtable -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\ConfigurationState" - } -MockWith $mockGetItemProperty_ConfigurationState -Verifiable - - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' - } -MockWith $mockGetItemProperty_Setup -Verifiable + Mock -CommandName Get-CimInstance -MockWith $mockEmptyHashtable -Verifiable } It 'Should set the system in the desired state when feature is SQLENGINE' { - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/AGTSVCSTARTUPTYPE=Automatic', - '/InstanceName="TEST"', - '/Features="SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS"', - '/SQLSysAdminAccounts="COMPANY\sqladmin"', - '/ASSysAdminAccounts="COMPANY\sqladmin"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + AGTSVCSTARTUPTYPE = 'Automatic' + InstanceName = 'TEST' + Features = 'SQLENGINE,REPLICATION,FULLTEXT,RS,IS,AS' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin' + } { 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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 0 -Scope It - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -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 Assert-MockCalled -CommandName StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1613,14 +2963,14 @@ try if( $mockSqlMajorVersion -eq 13 ) { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = $($testParameters.Features), 'SSMS' -join ',' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } It 'Should throw when feature parameter contains ''ADV_SSMS'' when installing SQL Server 2016' { $testParameters.Features = $($testParameters.Features), 'ADV_SSMS' -join ',' - $mockStartWin32ProcessExpectedArgument = '' + $mockStartWin32ProcessExpectedArgument = @{} { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } @@ -1628,28 +2978,33 @@ try It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="TEST"', - '/Features="SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'TEST' + Features = 'SSMS' + } { 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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -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 StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1659,27 +3014,32 @@ try It 'Should set the system in the desired state when feature is ADV_SSMS' { $testParameters.Features = 'ADV_SSMS' - $mockStartWin32ProcessExpectedArgument = - '/Quiet="True"', - '/IAcceptSQLServerLicenseTerms="True"', - '/Action="Install"', - '/InstanceName="TEST"', - '/Features="ADV_SSMS"' -join ' ' + $mockStartWin32ProcessExpectedArgument = @{ + Quiet = 'True' + IAcceptSQLServerLicenseTerms = 'True' + Action = 'Install' + InstanceName = 'TEST' + Features = 'ADV_SSMS' + } { 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) + 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-WmiObject -ParameterFilter { $Class -eq 'Win32_Service' } ` - -Exactly -Times 0 -Scope It - - Assert-MockCalled -CommandName Get-WmiObject -ParameterFilter { $Class -eq 'Win32_Product' } ` - -Exactly -Times 1 -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 StartWin32Process -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It @@ -1687,10 +3047,491 @@ try } } } + + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state and the action is PrepareFailoverCluster" { + BeforeAll { + $testParameters = $mockDefaultParameters.Clone() + $testParameters.Remove('Features') + $testParameters.Remove('SourceCredential') + $testParameters.Remove('ASSysAdminAccounts') + + $testParameters += @{ + Features = 'SQLENGINE' + InstanceName = 'MSSQLSERVER' + SourcePath = $mockSourcePath + Action = 'PrepareFailoverCluster' + } + + Mock -CommandName NetUse -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_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 StartWin32Process -MockWith $mockStartWin32Process -Verifiable + + Mock -CommandName Get-CimInstance -MockWith {} -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_ResourceGroup') -and ($Filter -eq "Name = 'Available Storage'") + } -Verifiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -ParameterFilter { + ($Association -eq 'MSCluster_ResourceGroupToResource') -and ($ResultClassName -eq 'MSCluster_Resource') + } -Verfiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -ParameterFilter { + $Association -eq 'MSCluster_ResourceToPossibleOwner' + } -Verifiable + + Mock -CommandName Get-CimAssociatedInstance -MockWith {} -ParameterFilter { + $ResultClass -eq 'MSCluster_DiskPartition' + } -Verifiable + + Mock -CommandName Get-CimInstance -MockWith {} -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_Network') -and ($Filter -eq 'Role >= 2') + } -Verifiable + } + + It 'Should add the SkipRules parameter to the installation arguments' { + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + Action = 'PrepareFailoverCluster' + SkipRules = 'Cluster_VerifyForErrors' + } + + { 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 StartWin32Process -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName WaitForWin32ProcessEnd -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_ResourceGroup') -and ($Filter -eq "Name = 'Available Storage'") + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimAssociatedInstance -ParameterFilter { + ($Association -eq 'MSCluster_ResourceGroupToResource') -and ($ResultClassName -eq 'MSCluster_Resource') + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimAssociatedInstance -ParameterFilter { + $Association -eq 'MSCluster_ResourceToPossibleOwner' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimAssociatedInstance -ParameterFilter { + $ResultClass -eq 'MSCluster_DiskPartition' + } -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { + ($Namespace -eq 'root/MSCluster') -and ($ClassName -eq 'MSCluster_Network') -and ($Filter -eq 'Role >= 2') + } -Exactly -Times 0 -Scope It + } + } + + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state and the action is CompleteFailoverCluster." { + BeforeEach { + $testParameters = $mockDefaultClusterParameters.Clone() + + $testParameters += @{ + InstanceName = 'MSSQLSERVER' + SourcePath = $mockSourcePath + Action = 'CompleteFailoverCluster' + FailoverClusterGroupName = 'SQL Server (MSSQLSERVER)' + FailoverClusterNetworkName = 'TestCluster' + FailoverClusterIPAddress = '10.0.0.100' + } + } + + BeforeAll { + 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') + } -Verfiable + + 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 + } + + It 'Should throw an error when one or more paths are not resolved to clustered storage' { + $badPathParameters = $testParameters.Clone() + + # Pass in a bad path + $badPathParameters.SQLUserDBDir = 'C:\MSSQL\' + + { Set-TargetResource @badPathParameters } | Should Throw 'Unable to map the specified paths to valid cluster storage. Drives mapped: TempDbData; TempDbLogs; UserLogs' + } + + It 'Should properly map paths to clustered disk resources' { + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + Action = 'CompleteFailoverCluster' + FailoverClusterIPAddresses = 'IPV4; 10.0.0.100; SiteA_Prod; 255.255.255.0' + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + SkipRules = 'Cluster_VerifyForErrors' + + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should build a DEFAULT address string when no network is specified' { + $missingNetworkParams = $testParameters.Clone() + $missingNetworkParams.Remove('FailoverClusterIPAddress') + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + Action = 'CompleteFailoverCluster' + FailoverClusterIPAddresses = 'DEFAULT' + + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + SkipRules = 'Cluster_VerifyForErrors' + } + + { Set-TargetResource @missingNetworkParams } | Should Not Throw + } + + It 'Should throw an error when an invalid IP Address is specified' { + $invalidAddressParameters = $testParameters.Clone() + + $invalidAddressParameters.Remove('FailoverClusterIPAddress') + $invalidAddressParameters += @{ + FailoverClusterIPAddress = '192.168.0.100' + } + + { Set-TargetResource @invalidAddressParameters } | Should Throw 'Unable to map the specified IP Address(es) to valid cluster networks.' + } + + It 'Should throw an error when an invalid IP Address is specified for a multi-subnet instance' { + $invalidAddressParameters = $testParameters.Clone() + + $invalidAddressParameters.Remove('FailoverClusterIPAddress') + $invalidAddressParameters += @{ + FailoverClusterIPAddress = @('10.0.0.100','192.168.0.100') + } + + { Set-TargetResource @invalidAddressParameters } | Should Throw 'Unable to map the specified IP Address(es) to valid cluster networks.' + } + + It 'Should build a valid IP address string for a single address' { + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + FailoverClusterIPAddresses = 'IPv4; 10.0.0.100; SiteA_Prod; 255.255.255.0' + + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + SkipRules = 'Cluster_VerifyForErrors' + Action = 'CompleteFailoverCluster' + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should build a valid IP address string for a multi-subnet cluster' { + $multiSubnetParameters = $testParameters.Clone() + $multiSubnetParameters.Remove('FailoverClusterIPAddress') + $multiSubnetParameters += @{ + FailoverClusterIPAddress = ($mockClusterSites | ForEach-Object { $_.Address }) + } + + $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() + $mockStartWin32ProcessExpectedArgument += @{ + FailoverClusterIPAddresses = 'IPv4; 10.0.0.100; SiteA_Prod; 255.255.255.0; IPv4; 10.0.10.100; SiteB_Prod; 255.255.255.0' + + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + SkipRules = 'Cluster_VerifyForErrors' + Action = 'CompleteFailoverCluster' + } + + { Set-TargetResource @multiSubnetParameters } | Should Not Throw + } + + It 'Should pass proper parameters to setup' { + $mockStartWin32ProcessExpectedArgument = @{ + IAcceptSQLServerLicenseTerms = 'True' + SkipRules = 'Cluster_VerifyForErrors' + Quiet = 'True' + AgtSvcStartupType = 'Automatic' + SQLSysAdminAccounts = 'COMPANY\sqladmin' + + Action = 'CompleteFailoverCluster' + InstanceName = 'MSSQLSERVER' + Features = 'SQLEngine' + FailoverClusterDisks = 'TempDbData; TempDbLogs; UserData; UserLogs' + FailoverClusterIPAddresses = 'IPV4; 10.0.0.100; SiteA_Prod; 255.255.255.0' + FailoverClusterGroup = 'SQL Server (MSSQLSERVER)' + SQLUserDBDir = 'K:\MSSQL\Data' + SQLUserDBLogDir = 'L:\MSSQL\Logs' + SQLTempDBDir = 'M:\MSSQL\TempDb\Data' + SQLTempDBLogDir = 'N:\MSSQL\TempDb\Logs' + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + } + } - + Assert-VerifiableMocks } + + # 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 $mockStartProcess -Verifiable + } + + + It 'Should use Unbuffered IO when copying' { + $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + + $mockStartProcessExpectedArgument = + $mockRobocopyArgumentSourcePath, + $mockRobocopyArgumentDestinationPath, + $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' { + $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithoutUnbufferedIO + + $mockStartProcessExpectedArgument = + $mockRobocopyArgumentSourcePath, + $mockRobocopyArgumentDestinationPath, + $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 { + $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess_WithExitCode -Verifiable + } + + It 'Should throw the correct error message when error code is 8' { + $mockStartProcessExitCode = 8 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported errors when copying files. Error code: $mockStartProcessExitCode." + + 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' { + $mockStartProcessExitCode = 16 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported errors when copying files. Error code: $mockStartProcessExitCode." + + 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)' { + $mockStartProcessExitCode = 9 + + $copyItemWithRoboCopyParameter = @{ + Path = $mockRobocopyArgumentSourcePath + DestinationPath = $mockRobocopyArgumentDestinationPath + } + + { Copy-ItemWithRoboCopy @copyItemWithRoboCopyParameter } | Should Throw "Robocopy reported that failures occured when copying files. Error code: $mockStartProcessExitCode." + + 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 succesfully it should return the correct exit code' { + BeforeEach { + $mockRobocopyExectuableVersion = $mockRobocopyExectuableVersionWithUnbufferedIO + + Mock -CommandName Get-Command -MockWith $mockGetCommand -Verifiable + Mock -CommandName Start-Process -MockWith $mockStartProcess_WithExitCode -Verifiable + } + + It 'Should finish succesfully with exit code 1' { + $mockStartProcessExitCode = 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 succesfully with exit code 2' { + $mockStartProcessExitCode = 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 succesfully with exit code 3' { + $mockStartProcessExitCode = 3 + + $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 + } + } + } + + Describe 'Get-ServiceAccountParameters' -Tag 'Helper' { + $serviceTypes = @('SQL','AGT','IS','RS','AS','FT') + + BeforeAll { + $mockServiceAccountPassword = ConvertTo-SecureString 'Password' -AsPlainText -Force + + $mockSystemServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'NT AUTHORITY\SYSTEM', $mockServiceAccountPassword + ) + + $mockVirtualServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'NT SERVICE\MSSQLSERVER', $mockServiceAccountPassword + ) + + $mockManagedServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'COMPANY\ManagedAccount$', $mockServiceAccountPassword + ) + + $mockDomainServiceAccount = ( + New-Object System.Management.Automation.PSCredential 'COMPANY\sql.service', $mockServiceAccountPassword + ) + } + + $serviceTypes | ForEach-Object { + + $serviceType = $_ + + Context "When service type is $serviceType" { + + It "Should return the correct parameters when the account is a system account." { + $result = Get-ServiceAccountParameters -ServiceAccount $mockSystemServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockSystemServiceAccount.UserName + $result.ContainsKey("$($serviceType)SVCPASSWORD") | Should Be $false + } + + It "Should return the correct parameters when the account is a virtual service account" { + $result = Get-ServiceAccountParameters -ServiceAccount $mockVirtualServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockVirtualServiceAccount.UserName + $result.ContainsKey("$($serviceType)SVCPASSWORD") | Should Be $false + } + + It "Should return the correct parameters when the account is a managed service account" { + $result = Get-ServiceAccountParameters -ServiceAccount $mockManagedServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockManagedServiceAccount.UserName + $result.ContainsKey("$($serviceType)SVCPASSWORD") | Should Be $false + } + + It "Should return the correct parameters when the account is a domain account" { + $result = Get-ServiceAccountParameters -ServiceAccount $mockDomainServiceAccount -ServiceType $serviceType + + $result.$("$($serviceType)SVCACCOUNT") | Should BeExactly $mockDomainServiceAccount.UserName + $result.$("$($serviceType)SVCPASSWORD") | Should BeExactly $mockDomainServiceAccount.GetNetworkCredential().Password + } + } + } + } } } finally diff --git a/Tests/Unit/Stubs/SMO.cs b/Tests/Unit/Stubs/SMO.cs index 84b9a1086..bf50586ef 100644 --- a/Tests/Unit/Stubs/SMO.cs +++ b/Tests/Unit/Stubs/SMO.cs @@ -2,11 +2,23 @@ using System; using System.Collections.Generic; +using System.Security; +using System.Runtime.InteropServices; namespace Microsoft.SqlServer.Management.Smo { #region Public Enums + // TypeName: Microsoft.SqlServer.Management.Smo.LoginCreateOptions + // Used by: + // MSFT_xSQLServerLogin.Tests.ps1 + public enum LoginCreateOptions + { + None = 0, + IsHashed = 1, + MustChange = 2 + } + // TypeName: Microsoft.SqlServer.Management.Smo.LoginType // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase // Used by: @@ -118,6 +130,45 @@ public ServerPermissionInfo( public string PermissionState = "Grant"; } + // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionSet + // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase + // Used by: + // xSQLServerDatabasePermission.Tests.ps1 + public class DatabasePermissionSet + { + public DatabasePermissionSet(){} + + public DatabasePermissionSet( bool connect, bool update ) + { + this.Connect = connect; + this.Update = update; + } + + public bool Connect = false; + public bool Update = false; + } + + // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo + // BaseType: Microsoft.SqlServer.Management.Smo.PermissionInfo + // Used by: + // xSQLServerDatabasePermission.Tests.ps1 + public class DatabasePermissionInfo + { + public DatabasePermissionInfo() + { + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet() }; + this.PermissionType = permissionSet; + } + + public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet ) + { + this.PermissionType = permissionSet; + } + + public Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] PermissionType; + public string PermissionState = "Grant"; + } + // TypeName: Microsoft.SqlServer.Management.Smo.Server // BaseType: Microsoft.SqlServer.Management.Smo.SqlSmoObject // Used by: @@ -184,17 +235,80 @@ public class Login { private bool _mockPasswordPassed = false; - public Login( Server server, string name ) { - this.Name = name; - } - - public Login( Object server, string name ) { - this.Name = name; - } - public string Name; public LoginType LoginType = LoginType.Unknown; + public bool MustChangePassword = false; + public bool PasswordPolicyEnforced = false; + public bool PasswordExpirationEnabled = false; + + public string MockName; + public LoginType MockLoginType; + + public Login( Server server, string name ) + { + this.Name = name; + } + + public Login( Object server, string name ) + { + this.Name = name; + } + + public void Alter() + { + if( !( String.IsNullOrEmpty(this.MockName) ) ) + { + if(this.MockName != this.Name) + { + throw new Exception(); + } + } + if( !( String.IsNullOrEmpty(this.MockLoginType.ToString()) ) ) + { + if( this.MockLoginType != this.LoginType ) + { + throw new Exception(this.MockLoginType.ToString()); + } + } + } + + public void ChangePassword( SecureString secureString ) + { + IntPtr valuePtr = IntPtr.Zero; + try + { + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(secureString); + if ( Marshal.PtrToStringUni(valuePtr) == "pw" ) + { + throw new FailedOperationException ( + "FailedOperationException", + new SmoException ( + "SmoException", + new SqlServerManagementException ( + "SqlServerManagementException", + new Exception ( + "Password validation failed. The password does not meet Windows policy requirements because it is too short." + ) + ) + ) + ); + } + else if ( Marshal.PtrToStringUni(valuePtr) == "reused" ) + { + throw new FailedOperationException (); + } + else if ( Marshal.PtrToStringUni(valuePtr) == "other" ) + { + throw new Exception (); + } + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); + } + } + public void Create() { if( this.LoginType == LoginType.Unknown ) { @@ -204,17 +318,90 @@ public void Create() if( this.LoginType == LoginType.SqlLogin && _mockPasswordPassed != true ) { throw new System.Exception( "Called Create() method for the LoginType 'SqlLogin' but called with the wrong overloaded method. Did not pass the password with the Create() method." ); } + + if( !( String.IsNullOrEmpty(this.MockName) ) ) + { + if(this.MockName != this.Name) + { + throw new Exception(); + } + } + + if( !( String.IsNullOrEmpty(this.MockLoginType.ToString()) ) ) + { + if( this.MockLoginType != this.LoginType ) + { + throw new Exception(this.MockLoginType.ToString()); + } + } } - public void Create( String secureString ) + public void Create( SecureString secureString ) { _mockPasswordPassed = true; this.Create(); } + public void Create( SecureString password, LoginCreateOptions options ) + { + IntPtr valuePtr = IntPtr.Zero; + try + { + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password); + if ( Marshal.PtrToStringUni(valuePtr) == "pw" ) + { + throw new FailedOperationException ( + "FailedOperationException", + new SmoException ( + "SmoException", + new SqlServerManagementException ( + "SqlServerManagementException", + new Exception ( + "Password validation failed. The password does not meet Windows policy requirements because it is too short." + ) + ) + ) + ); + } + else if ( this.Name == "Existing" ) + { + throw new FailedOperationException ( "The login already exists" ); + } + else if ( this.Name == "Unknown" ) + { + throw new Exception (); + } + else + { + _mockPasswordPassed = true; + + this.Create(); + } + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); + } + } + public void Drop() { + if( !( String.IsNullOrEmpty(this.MockName) ) ) + { + if(this.MockName != this.Name) + { + throw new Exception(); + } + } + + if( !( String.IsNullOrEmpty(this.MockLoginType.ToString()) ) ) + { + if( this.MockLoginType != this.LoginType ) + { + throw new Exception(this.MockLoginType.ToString()); + } + } } } @@ -240,8 +427,11 @@ public ServerRole( Object server, string name ) { // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase // Used by: // MSFT_xSQLServerDatabase + // MSFT_xSQLServerDatabasePermission public class Database { + public string MockGranteeName; + public Database( Server server, string name ) { this.Name = name; } @@ -259,6 +449,44 @@ public void Create() public void Drop() { } + + public Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] EnumDatabasePermissions( string granteeName ) + { + List listOfDatabasePermissionInfo = new List(); + + if( Globals.GenerateMockData ) { + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { + new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( true, false ), + new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( false, true ) + }; + + listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo( permissionSet ) ); + } else { + listOfDatabasePermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo() ); + } + + Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] permissionInfo = listOfDatabasePermissionInfo.ToArray(); + + return permissionInfo; + } + + public void Grant( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permission, string granteeName ) + { + if( granteeName != this.MockGranteeName ) + { + string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; + throw new System.ArgumentException(errorMessage, "granteeName"); + } + } + + public void Deny( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permission, string granteeName ) + { + if( granteeName != this.MockGranteeName ) + { + string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; + throw new System.ArgumentException(errorMessage, "granteeName"); + } + } } // TypeName: Microsoft.SqlServer.Management.Smo.User @@ -267,14 +495,16 @@ public void Drop() // xSQLServerDatabaseRole.Tests.ps1 public class User { - public User( Server server, string name ) { + public User( Server server, string name ) + { this.Name = name; } - public User( Object server, string name ) { + public User( Object server, string name ) + { this.Name = name; - } - + } + public string Name; public string Login; @@ -287,5 +517,44 @@ public void Drop() } } + // TypeName: Microsoft.SqlServer.Management.Smo.SqlServerManagementException + // BaseType: System.Exception + // Used by: + // xSqlServerLogin.Tests.ps1 + public class SqlServerManagementException : Exception + { + public SqlServerManagementException () : base () {} + + public SqlServerManagementException (string message) : base (message) {} + + public SqlServerManagementException (string message, Exception inner) : base (message, inner) {} + } + + // TypeName: Microsoft.SqlServer.Management.Smo.SmoException + // BaseType: Microsoft.SqlServer.Management.Smo.SqlServerManagementException + // Used by: + // xSqlServerLogin.Tests.ps1 + public class SmoException : SqlServerManagementException + { + public SmoException () : base () {} + + public SmoException (string message) : base (message) {} + + public SmoException (string message, SqlServerManagementException inner) : base (message, inner) {} + } + + // TypeName: Microsoft.SqlServer.Management.Smo.FailedOperationException + // BaseType: Microsoft.SqlServer.Management.Smo.SmoException + // Used by: + // xSqlServerLogin.Tests.ps1 + public class FailedOperationException : SmoException + { + public FailedOperationException () : base () {} + + public FailedOperationException (string message) : base (message) {} + + public FailedOperationException (string message, SmoException inner) : base (message, inner) {} + } + #endregion Public Classes } diff --git a/Tests/Unit/Stubs/SqlPowerShellSqlExecutionException.cs b/Tests/Unit/Stubs/SqlPowerShellSqlExecutionException.cs new file mode 100644 index 000000000..27cbaf0cb --- /dev/null +++ b/Tests/Unit/Stubs/SqlPowerShellSqlExecutionException.cs @@ -0,0 +1,9 @@ +namespace Microsoft.SqlServer.Management.PowerShell +{ + public class SqlPowerShellSqlExecutionException : System.Exception + { + public SqlPowerShellSqlExecutionException() + { + } + } +} diff --git a/Tests/Unit/xSQLServerHelper.Tests.ps1 b/Tests/Unit/xSQLServerHelper.Tests.ps1 index 078e07091..875d75a4b 100644 --- a/Tests/Unit/xSQLServerHelper.Tests.ps1 +++ b/Tests/Unit/xSQLServerHelper.Tests.ps1 @@ -4,7 +4,7 @@ $script:moduleName = 'xSQLServerHelper' [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'))) ) + (-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\')) } @@ -12,6 +12,9 @@ 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 Import-Module (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent | Split-Path -Parent) -ChildPath 'xSQLServerHelper.psm1') -Scope Global -Force +# Loading mocked classes +Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + # Begin Testing InModuleScope $script:moduleName { Describe 'Testing Restart-SqlService' { @@ -201,4 +204,325 @@ InModuleScope $script:moduleName { } } } + + Describe "Testing Get-SqlDatabasePermission" { + $mockSqlServerObject = [pscustomobject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + } + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + 'AdventureWorks' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') ) ) + } | Add-Member -MemberType ScriptMethod -Name EnumDatabasePermissions -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( 'Connect','Update' ) + } + } -PassThru -Force + } -PassThru -Force + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'CONTOSO\SqlAdmin') -Property @{ LoginType = 'WindowsUser'} ) ) + } + } -PassThru -Force + + + Context 'When the specified database does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'UnknownDatabase' + PermissionState = 'Grant' + } + + It 'Should throw the correct error' { + { Get-SqlDatabasePermission @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified login does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\UnknownUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + } + + It 'Should throw the correct error' { + { Get-SqlDatabasePermission @testParameters } | Should Throw "Login 'CONTOSO\UnknownUser' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified database and login exist and the system is not in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + } + + It 'Should not return any permissions' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false + + $permission = Get-SqlDatabasePermission @testParameters + $permission | Should BeNullOrEmpty + } + + } + + Context 'When the specified database and login exist and the system is in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + } + + It 'Should return the correct permissions' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + + $permission = Get-SqlDatabasePermission @testParameters + $permission -contains 'Connect' | Should Be $true + $permission -contains 'Update' | Should Be $true + } + } + + Assert-VerifiableMocks + } + + Describe "Testing Get-SqlDatabaseRecoveryModel" { + $mockSqlServerObject = [pscustomobject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + Databases = @{ + AdventureWorks = @{ + RecoveryModel = 'Full' + } + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'UnknownDatabase' + } + + It 'Should throw the correct error' { + { Get-SqlDatabaseRecoveryModel @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified database exist' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'AdventureWorks' + } + + It 'Should return the current RecoveryModel' { + $recoveryModel = Get-SqlDatabaseRecoveryModel @testParameters + $recoveryModel | Should Be $testParameters.SqlServerObject.Databases.AdventureWorks.RecoveryModel + } + } + + Assert-VerifiableMocks + } + + Describe "Testing Set-SqlDatabaseRecoveryModel" { + $mockSqlServerObject = [pscustomobject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + Databases = @{ + AdventureWorks = @{ + RecoveryModel = 'Full' + } | Add-Member -MemberType ScriptMethod -Name Alter -Value { + if ( $this.RecoveryModel -ne $mockExpectedRecoveryModelForAlterMethod ) + { + throw "Called mocked Alter() method without setting the right RecoveryModel. Expected '{0}'. But was '{1}'." -f $mockExpectedRecoveryModelForAlterMethod, $this.RecoveryModel + } + } -PassThru -Force + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'UnknownDatabase' + RecoveryModel = 'Simple' + } + + It 'Should throw the correct error' { + { Set-SqlDatabaseRecoveryModel @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified database and the system is not in desired state' { + $testParameters = @{ + SqlServerObject = $mockSqlServerObject + DatabaseName = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + It 'Should set the correct RecoveryModel without throwing an error' { + $mockExpectedRecoveryModelForAlterMethod = $testParameters.RecoveryModel + { Set-SqlDatabaseRecoveryModel @testParameters } | Should Not Throw + } + } + + Assert-VerifiableMocks + } + + Describe "Testing Add-SqlDatabasePermission" { + $mockSqlServerObject = [PSCustomObject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + } + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + 'AdventureWorks' = @( + ( + New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') | + Add-Member -MemberType ScriptProperty -Name Users -Value { + return @{ + 'CONTOSO\SqlAdmin' = $true + 'CONTOSO\UnknownUser' = $false + } + } -PassThru -Force + ) + ) + } | Add-Member -MemberType ScriptMethod -Name EnumDatabasePermissions -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( 'Connect','Update' ) + } + } -PassThru -Force + } -PassThru -Force + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'CONTOSO\SqlAdmin') -Property @{ LoginType = 'WindowsUser'} ) ) + } + } -PassThru -Force + + Context 'When the specified database and login exist and the system is not in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should add permissions to the specified database' { + Add-SqlDatabasePermission @testParameters + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'UnknownDatabase' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Add-SqlDatabasePermission @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified login does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\UnknownUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Add-SqlDatabasePermission @testParameters } | Should Throw "Login 'CONTOSO\UnknownUser' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Assert-VerifiableMocks + } + + Describe "Testing Remove-SqlDatabasePermission" { + $mockSqlServerObject = [PSCustomObject]@{ + InstanceName = 'MSSQLSERVER' + ComputerNamePhysicalNetBIOS = 'SQL01' + } + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + 'AdventureWorks' = @( + ( + New-Object Microsoft.SqlServer.Management.Smo.Database -ArgumentList @( $null, 'AdventureWorks') | + Add-Member -MemberType ScriptProperty -Name Users -Value { + return @{ + 'CONTOSO\SqlAdmin' = $true + 'CONTOSO\UnknownUser' = $false + } + } -PassThru -Force + ) + ) + } | Add-Member -MemberType ScriptMethod -Name EnumDatabasePermissions -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( 'Connect','Update' ) + } + } -PassThru -Force + } -PassThru -Force + + $mockSqlServerObject = $mockSqlServerObject | Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + 'CONTOSO\SqlAdmin' = @( ( New-Object Microsoft.SqlServer.Management.Smo.Login -ArgumentList @( $null, 'CONTOSO\SqlAdmin') -Property @{ LoginType = 'WindowsUser'} ) ) + } + } -PassThru -Force + + Context 'When the specified database and login exist and the system is not in desired state' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should remove permissions to the specified database' { + Remove-SqlDatabasePermission @testParameters + } + } + + Context 'When the specified database does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\SqlAdmin' + Database = 'UnknownDatabase' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Remove-SqlDatabasePermission @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Context 'When the specified login does not exist' { + $testParameters = @{ + Sql = $mockSqlServerObject + Name = 'CONTOSO\UnknownUser' + Database = 'AdventureWorks' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should throw the correct error' { + { Remove-SqlDatabasePermission @testParameters } | Should Throw "Login 'CONTOSO\UnknownUser' does not exist on SQL server 'SQL01\MSSQLSERVER'." + } + } + + Assert-VerifiableMocks + } } diff --git a/Tests/xSQLServerCommon.Tests.ps1 b/Tests/xSQLServerCommon.Tests.ps1 new file mode 100644 index 000000000..e8a8355f3 --- /dev/null +++ b/Tests/xSQLServerCommon.Tests.ps1 @@ -0,0 +1,171 @@ +[CmdletBinding()] +# Suppressing this because we need to generate a mocked credentials that will be passed along to the examples that are needed in the tests. +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] +param() + +$script:moduleRoot = Split-Path $PSScriptRoot -Parent + +Describe 'xSQLServer module common tests' { + Context -Name 'When there are example file for resource' { + <# + For Appveyor builds copy the module to the system modules directory so it falls + in to a PSModulePath folder and is picked up correctly. + #> + if ($env:APPVEYOR) + { + $powershellModulePath = Join-Path -Path (($env:PSModulePath -split ';')[0]) -ChildPath 'xSQLServer' + Copy-item -Path $env:APPVEYOR_BUILD_FOLDER -Destination $powershellModulePath -Recurse -Force + } + + $mockPassword = ConvertTo-SecureString '&iPm%M5q3K$Hhq=wcEK' -AsPlainText -Force + $mockCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @('username', $mockPassword) + $mockConfigData = @{ + AllNodes = @( + @{ + NodeName = "localhost" + PSDscAllowPlainTextPassword = $true + } + ) + } + + $exampleFile = Get-ChildItem -Path "$script:moduleRoot\Examples\Resources" -Filter "*.ps1" -Recurse + + foreach ($exampleToValidate in $exampleFile) + { + $exampleDescriptiveName = Join-Path -Path (Split-Path $exampleToValidate.Directory -Leaf) -ChildPath (Split-Path $exampleToValidate -Leaf) + + It "Should compile MOFs for example '$exampleDescriptiveName' correctly" { + { + . $exampleToValidate.FullName + + $exampleCommand = Get-Command Example -ErrorAction SilentlyContinue + if ($exampleCommand) + { + try + { + $params = @{} + $exampleCommand.Parameters.Keys | Where-Object { $_ -like '*Account' -or ($_ -like '*Credential' -and $_ -ne 'PsDscRunAsCredential') } | ForEach-Object -Process { + $params.Add($_, $mockCredential) + } + + Example @params -ConfigurationData $mockConfigData -OutputPath 'TestDrive:\' -ErrorAction Continue -WarningAction SilentlyContinue | Out-Null + } + finally + { + # Remove the function we dot-sourced so next example file doesn't use the previous Example-function. + Remove-Item function:Example + } + } + else + { + throw "The example '$exampleDescriptiveName' does not contain a function 'Example'." + } + } | Should Not Throw + } + } + + if ($env:APPVEYOR -eq $true) + { + Remove-item -Path $powershellModulePath -Recurse -Force -Confirm:$false + + # Restore the module in 'memory' to ensure other tests after this test have access to it + Import-Module -Name "$script:moduleRoot\xSQLServer.psd1" -Global -Force + } + } + + Context -Name 'When there are Markdown files in the module' { + if (Get-Command npm) + { + It 'Should not throw an error when installing dependencies' { + { + <# + gulp; gulp is a toolkit that helps you automate painful or time-consuming tasks in your development workflow. + gulp must be installed globally to be able to be called through Start-Process + #> + Start-Process -FilePath "npm" -ArgumentList "install -g gulp" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # gulp must also be installed locally to be able to be referenced in the javascript file. + Start-Process -FilePath "npm" -ArgumentList "install gulp" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # Used in gulpfile.js; A tiny wrapper around Node streams2 Transform to avoid explicit subclassing noise + Start-Process -FilePath "npm" -ArgumentList "install through2" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # Used in gulpfile.js; A Node.js style checker and lint tool for Markdown/CommonMark files. + Start-Process -FilePath "npm" -ArgumentList "install markdownlint" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + + # gulp-concat is installed as devDependencies. Used in gulpfile.js; Concatenates files + Start-Process -FilePath "npm" -ArgumentList "install gulp-concat -D" -WorkingDirectory $script:moduleRoot -Wait -WindowStyle Hidden + } | Should Not Throw + } + + It 'Should not have an error in any Markdown files' { + $markdownError = 0 + + try + { + # This executes the gulpfile.js in the root folder of the module. + Start-Process -FilePath "gulp" -ArgumentList "test-mdsyntax --silent" -WorkingDirectory $script:moduleRoot -Wait -NoNewWindow + + # Wait 3 seconds so the locks on file 'markdownerror.txt' has been released. + Start-Sleep -Seconds 3 + + $markdownFoundErrorPath = Join-Path -Path $script:moduleRoot -ChildPath "markdownerror.txt" + + if ((Test-Path -Path $markdownFoundErrorPath)) + { + Get-Content -Path $markdownFoundErrorPath | ForEach-Object -Process { + if (-not [string]::IsNullOrEmpty($_)) + { + Write-Warning -Message $_ + + $markdownError++ + } + } + } + + <# + When running in AppVeyor. Wait 5 seconds so the output have time to be sent to AppVeyor Console. + If there are many errors, the AppVeyor Console doesn't have time to print all warning messages + and messages comes out of order. + #> + if( $env:APPVEYOR ) + { + Start-Sleep -Seconds 5 + } + } + catch [System.Exception] + { + Write-Warning -Message "Unable to run gulp to test Markdown files. Please be sure that you have installed node.js. Error: $_" + } + + # Removes the 'markdownerror.txt' file from the module root so it is not shipped. + Remove-Item -Path $markdownFoundErrorPath -Force -ErrorAction SilentlyContinue + + # The actual test that will fail the build if it is not zero. + $markdownError | Should Be 0 + } + + It 'Should not throw an error when uninstalling dependencies' { + { + # Uninstalled npm packages in reverse order + Start-Process -FilePath "npm" -ArgumentList "uninstall gulp-concat -D" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall markdownlint" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall through2" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall gulp" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + Start-Process -FilePath "npm" -ArgumentList "uninstall -g gulp" -WorkingDirectory $script:moduleRoot -Wait -PassThru -WindowStyle Hidden + + # Remove folder node_modules that npm created. + $npmNpdeModulesPath = (Join-Path -Path $script:moduleRoot -ChildPath 'node_modules') + if( Test-Path -Path $npmNpdeModulesPath) + { + Remove-Item -Path $npmNpdeModulesPath -Recurse -Force + } + } | Should Not Throw + } + } + else + { + Write-Warning -Message 'Cannot find npm to install dependencies needed to test markdown files. Skipping this test!' + } + } +} diff --git a/appveyor.yml b/appveyor.yml index ceed152e3..7410e5359 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,64 +1,80 @@ -#---------------------------------# -# environment configuration # -#---------------------------------# -version: 4.0.{build}.0 -install: +#---------------------------------# +# environment configuration # +#---------------------------------# +version: 5.0.{build}.0 +install: - git clone https://github.com/PowerShell/DscResource.Tests + - appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/v3.4.4/NuGet.exe - ps: | Import-Module -Name .\DscResource.Tests\TestHelper.psm1 -Force Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Install-Module -Name Pester -Repository PSGallery -Force -#---------------------------------# -# build configuration # -#---------------------------------# +#---------------------------------# +# build configuration # +#---------------------------------# build: false -#---------------------------------# -# test configuration # -#---------------------------------# +#---------------------------------# +# test configuration # +#---------------------------------# test_script: - ps: | - $testResultsFile = ".\TestsResults.xml" - $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru + Write-Host 'Removing all SQL related PowerShell modules so they can not be used by the common tests.' + Write-Host 'This is a workaround because the AppVeyor build worker image contains SQL Server. More information in issue #239.' + Write-Host 'Modules that are being removed:' + Get-Module -ListAvailable -Name 'sql*' | ForEach-Object -Process { Write-Host $_.Path; Remove-Item $_.Path -Force; } + + # Start the tests + Write-Warning -Message 'Code coverage statistics are being calculated. This will slow the start of the tests while the code matrix is built. Please be patient.' + $testResultsFile = '.\TestsResults.xml' + $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru -CodeCoverage @("$env:APPVEYOR_BUILD_FOLDER\*.psm1","$env:APPVEYOR_BUILD_FOLDER\DSCResources\**\*.psm1") (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) - if ($res.FailedCount -gt 0) { + if ($res.FailedCount -gt 0) { throw "$($res.FailedCount) tests failed." } - -#---------------------------------# -# deployment configuration # -#---------------------------------# -# scripts to run before deployment -deploy_script: +#---------------------------------# +# deployment configuration # +#---------------------------------# + +# scripts to run before deployment +deploy_script: - ps: | # Creating project artifact $stagingDirectory = (Resolve-Path ..).Path $manifest = Join-Path $pwd "xSQLServer.psd1" - (Get-Content $manifest -Raw).Replace("4.0.0.0", $env:APPVEYOR_BUILD_VERSION) | Out-File $manifest + (Get-Content $manifest -Raw).Replace("5.0.0.0", $env:APPVEYOR_BUILD_VERSION) | Out-File $manifest $zipFilePath = Join-Path $stagingDirectory "$(Split-Path $pwd -Leaf).zip" Add-Type -assemblyname System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::CreateFromDirectory($pwd, $zipFilePath) - + # Creating NuGet package artifact New-Nuspec -packageName $env:APPVEYOR_PROJECT_NAME -version $env:APPVEYOR_BUILD_VERSION -author "Microsoft" -owners "Microsoft" -licenseUrl "https://github.com/PowerShell/DscResources/blob/master/LICENSE" -projectUrl "https://github.com/$($env:APPVEYOR_REPO_NAME)" -packageDescription $env:APPVEYOR_PROJECT_NAME -tags "DesiredStateConfiguration DSC DSCResourceKit" -destinationPath . - nuget pack ".\$($env:APPVEYOR_PROJECT_NAME).nuspec" -outputdirectory . + + # Force to use the downloaded Nuget v3.4.4, because newer versions of Nuget are failing the deploy step. + # See issue https://github.com/PowerShell/xNetworking/issues/177 + Start-Process -FilePath "$($env:APPVEYOR_BUILD_FOLDER)\nuget" -Wait -ArgumentList @( + "pack", + ".\$($env:APPVEYOR_PROJECT_NAME).nuspec", + "-outputdirectory $env:APPVEYOR_BUILD_FOLDER" + ) + $nuGetPackageName = $env:APPVEYOR_PROJECT_NAME + "." + $env:APPVEYOR_BUILD_VERSION + ".nupkg" $nuGetPackagePath = (Get-ChildItem $nuGetPackageName).FullName - + @( # You can add other artifacts here $zipFilePath, $nuGetPackagePath - ) | % { + ) | % { Write-Host "Pushing package $_ as Appveyor artifact" Push-AppveyorArtifact $_ } - - + + diff --git a/en-US/xSQLServer.strings.psd1 b/en-US/xSQLServer.strings.psd1 index 98adc8fa5..95a86b1f6 100644 --- a/en-US/xSQLServer.strings.psd1 +++ b/en-US/xSQLServer.strings.psd1 @@ -39,4 +39,22 @@ ConfigurationRestartRequired = Configuration option '{0}' has been updated, but # AlwaysOnService AlterAlwaysOnServiceFailed = Failed to ensure Always On is {0} on the instance '{1}'. + +# Login +PasswordValidationFailed = Creation of the login '{0}' failed due to the following error: {1} +LoginCreationFailedFailedOperation = Creation of the login '{0}' failed due to a failed operation. +LoginCreationFailedSqlNotSpecified = Creation of the SQL login '{0}' failed due to an unspecified error. +LoginCreationFailedWindowsNotSpecified = Creation of the Windows login '{0}' failed due to an unspecified error. +LoginTypeNotImplemented = The login type '{0}' is not implemented in this resource. +IncorrectLoginMode = The instance '{0}\{1}' is currently in '{2}' authentication mode. To create a SQL Login, it must be set to 'Mixed' authentication mode. +LoginCredentialNotFound = The credential for the SQL Login '{0}' was not found. +PasswordChangeFailed = Setting the password failed for the SQL Login '{0}'. +AlterLoginFailed = Altering the login '{0}' failed. +CreateLoginFailed = Creating the login '{0}' failed. +DropLoginFailed = Dropping the login '{0}' failed. + +# Clustered Setup +FailoverClusterDiskMappingError = Unable to map the specified paths to valid cluster storage. Drives mapped: {0} +FailoverClusterIPAddressNotValid = Unable to map the specified IP Address(es) to valid cluster networks. +FailoverClusterResourceNotFound = Could not locate a SQL Server cluster resource for instance {0}. '@ diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 000000000..43c257d0b --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,24 @@ +var gulp = require("gulp"); +var concat = require("gulp-concat"); +var through2 = require("through2"); +var markdownlint = require("markdownlint"); + +gulp.task("test-mdsyntax", function task() { + return gulp.src(["./DSCResources/**/*.md","./*.md"], { "read": false }) + .pipe(through2.obj(function obj(file, enc, next) { + markdownlint( + { + "files": [ file.path ], + "config": require("./.markdownlint.json") + }, + function callback(err, result) { + var resultString = (result || "").toString(); + if (resultString) { + file.contents = new Buffer(resultString); + } + next(err, file); + }); + })) + .pipe(concat("markdownerror.txt", { newLine: "\r\n" })) + .pipe(gulp.dest(".")); +}); diff --git a/xPDT.psm1 b/xPDT.psm1 index 6f22564c1..6334ef1a4 100644 --- a/xPDT.psm1 +++ b/xPDT.psm1 @@ -108,11 +108,11 @@ function ExtractArguments ( [parameter(Mandatory = $true)] $functionBoundParameters, - + [parameter(Mandatory = $true)] [string[]] $argumentNames, - + [string[]] $newArgumentNames ) @@ -160,7 +160,7 @@ namespace Source public static class NativeMethods { //The following structs and enums are used by the various Win32 API's that are used in the code below - + [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { @@ -259,25 +259,25 @@ namespace Source EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern bool CreateProcessAsUser( - IntPtr hToken, - string lpApplicationName, + IntPtr hToken, + string lpApplicationName, string lpCommandLine, - ref SECURITY_ATTRIBUTES lpProcessAttributes, + ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, - bool bInheritHandle, - Int32 dwCreationFlags, + bool bInheritHandle, + Int32 dwCreationFlags, IntPtr lpEnvrionment, - string lpCurrentDirectory, + string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation ); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] public static extern bool DuplicateTokenEx( - IntPtr hExistingToken, + IntPtr hExistingToken, Int32 dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, - Int32 ImpersonationLevel, + Int32 ImpersonationLevel, Int32 dwTokenType, ref IntPtr phNewToken ); @@ -294,11 +294,11 @@ namespace Source [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool AdjustTokenPrivileges( - IntPtr htok, + IntPtr htok, bool disall, - ref TokPriv1Luid newst, - int len, - IntPtr prev, + ref TokPriv1Luid newst, + int len, + IntPtr prev, IntPtr relen ); @@ -307,14 +307,14 @@ namespace Source [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool OpenProcessToken( - IntPtr h, - int acc, + IntPtr h, + int acc, ref IntPtr phtok ); [DllImport("advapi32.dll", SetLastError = true)] internal static extern bool LookupPrivilegeValue( - string host, + string host, string name, ref long pluid ); @@ -338,15 +338,15 @@ namespace Source LogonProvider.LOGON32_PROVIDER_DEFAULT, out hToken ); - if (!bResult) - { - throw new Win32Exception("The user could not be logged on. Ensure that the user has an existing profile on the machine and that correct credentials are provided. Logon error #" + Marshal.GetLastWin32Error().ToString()); + if (!bResult) + { + throw new Win32Exception("The user could not be logged on. Ensure that the user has an existing profile on the machine and that correct credentials are provided. Logon error #" + Marshal.GetLastWin32Error().ToString()); } IntPtr hproc = GetCurrentProcess(); IntPtr htok = IntPtr.Zero; bResult = OpenProcessToken( - hproc, - TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + hproc, + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok ); if(!bResult) @@ -357,8 +357,8 @@ namespace Source tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED; bResult = LookupPrivilegeValue( - null, - SE_INCRASE_QUOTA, + null, + SE_INCRASE_QUOTA, ref tp.Luid ); if(!bResult) @@ -366,18 +366,18 @@ namespace Source throw new Win32Exception("Error in looking up privilege of the process. This should not happen if DSC is running as LocalSystem Lookup privilege error #" + Marshal.GetLastWin32Error().ToString()); } bResult = AdjustTokenPrivileges( - htok, - false, - ref tp, - 0, - IntPtr.Zero, + htok, + false, + ref tp, + 0, + IntPtr.Zero, IntPtr.Zero ); if(!bResult) { throw new Win32Exception("Token elevation error #" + Marshal.GetLastWin32Error().ToString()); } - + bResult = DuplicateTokenEx( hToken, GENERIC_ALL_ACCESS, @@ -397,13 +397,13 @@ namespace Source hDupedToken, null, strCommand, - ref sa, ref sa, - false, - 0, + ref sa, + false, + 0, IntPtr.Zero, - null, - ref si, + null, + ref si, ref pi ); if(!bResult) @@ -495,7 +495,7 @@ function GetWin32ProcessOwner { return $Owner.Domain + "\" + $Owner.User } - else + else { return $Owner.User } @@ -643,7 +643,7 @@ function WaitForWin32ProcessStart { $value = @(GetWin32Process @GetArguments).Count -ge 1 } while(!$value -and ([DateTime]::Now - $start).TotalMilliseconds -lt $Delay) - + return $value } @@ -675,15 +675,15 @@ function WaitForWin32ProcessEnd function NetUse { param - ( + ( [parameter(Mandatory)] [string] $SourcePath, - + [parameter(Mandatory)] [PSCredential] $Credential, - + [string] [ValidateSet("Present","Absent")] $Ensure = "Present" @@ -691,46 +691,18 @@ function NetUse if(($SourcePath.Length -ge 2) -and ($SourcePath.Substring(0,2) -eq "\\")) { - $args = @() + $args = @() if ($Ensure -eq "Absent") { - $args += "use", $SourcePath, "/del" + $args += "use", $SourcePath, "/del" } - else + else { - $args += "use", $SourcePath, $($Credential.GetNetworkCredential().Password), "/user:$($Credential.GetNetworkCredential().Domain)\$($Credential.GetNetworkCredential().UserName)" + $args += "use", $SourcePath, $($Credential.GetNetworkCredential().Password), "/user:$($Credential.GetNetworkCredential().Domain)\$($Credential.GetNetworkCredential().UserName)" } - - &"net" $args - } -} -function GetxPDTVariable -{ - param - ( - [parameter(Mandatory = $true)] - [System.String] - $Component, - - [parameter(Mandatory = $true)] - [System.String] - $Version, - - [parameter(Mandatory = $true)] - [System.String] - $Role, - - [parameter(Mandatory = $true)] - [System.String] - $Name, - - [System.String] - $Update = "Latest" - ) - - $xPDT = [XML](Get-Content "$PSScriptRoot\xPDT.xml") - $xPDT.SelectSingleNode("//xPDT/Component[@Name='$Component' and @Version='$Version']/Role[@Name='$Role']/Update[@Name='$Update']/Variable[@Name='$Name']").Value + &"net" $args + } } -Export-ModuleMember ResolvePath,StartWin32Process,WaitForWin32ProcessEnd,NetUse,GetxPDTVariable +Export-ModuleMember ResolvePath,StartWin32Process,WaitForWin32ProcessEnd,NetUse diff --git a/xPDT.xml b/xPDT.xml deleted file mode 100644 index c0219c805..000000000 --- a/xPDT.xml +++ /dev/null @@ -1,1091 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xSQLServer.psd1 b/xSQLServer.psd1 index b5895c057..0020e8a4e 100644 --- a/xSQLServer.psd1 +++ b/xSQLServer.psd1 @@ -1,6 +1,6 @@ @{ # Version number of this module. -ModuleVersion = '4.0.0.0' +ModuleVersion = '5.0.0.0' # ID used to uniquely identify this module GUID = '74e9ddb5-4cbc-4fa2-a222-2bcfb533fd66' @@ -47,42 +47,111 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '- Fixes in xSQLServerConfiguration - - Added support for clustered SQL instances - - BREAKING CHANGE: Updated parameters to align with other resources (SQLServer / SQLInstanceName) - - Updated code to utilize CIM rather than WMI -- Added tests for resources - - xSQLServerConfiguration - - xSQLServerSetup - - xSQLServerDatabaseRole - - xSQLAOGroupJoin - - xSQLServerHelper and moved the existing tests for Restart-SqlService to it. - - xSQLServerAlwaysOnService -- Fixes in xSQLAOGroupJoin - - Availability Group name now appears in the error message for a failed Availability Group join attempt. - - Get-TargetResource now works with Get-DscConfiguration -- Fixes in xSQLServerRole - - Updated Ensure parameter to "Present" default value - - Renamed helper functions *-SqlServerRole to *-SqlServerRoleMember -- Changes to xSQLAlias - - Add UseDynamicTcpPort parameter for option "Dynamically determine port" - - Change Get-WmiObject to Get-CimInstance in Resource and associated pester file -- Added CHANGELOG.md file -- Added issue template file (ISSUE_TEMPLATE.md) for "New Issue" and pull request template file (PULL_REQUEST_TEMPLATE.md) for "New Pull Request" -- Add Contributing.md file + ReleaseNotes = '- Improvements how tests are initiated in AppVeyor + - Removed previous workaround (issue 201) from unit tests. + - Changes in appveyor.yml so that SQL modules are removed before common test is run. + - Now the deploy step are no longer failing when merging code into Dev. Neither is the deploy step failing if a contributor had AppVeyor connected to the fork of xSQLServer and pushing code to the fork. +- Changes to README.md + - Changed the contributing section to help new contributors. + - Added links for each resource so it is easier to navigate to the parameter list for each resource. + - Moved the list of resources in alphabetical order. + - Moved each resource parameter list into alphabetical order. + - Removed old text mentioning System Center. + - Now the correct product name is written in the installation section, and a typo was also fixed. + - Fixed a typo in the Requirements section. + - Added link to Examples folder in the Examples section. + - Change the layout of the README.md to closer match the one of PSDscResources + - Added more detailed text explaining what operating systemes WMF5.0 can be installed on. + - Verified all resource schema files with the README.md and fixed some errors (descriptions was not verified). + - Added security requirements section for resource xSQLServerEndpoint and xSQLAOGroupEnsure. - Changes to xSQLServerSetup - - Now `Features` parameter is case-insensitive. -- BREAKING CHANGE: Removed xSQLServerPowerPlan from this module. The resource has been moved to [xComputerManagement](https://github.com/PowerShell/xComputerManagement) and is now called xPowerPlan. -- Changes and enhancements in xSQLServerDatabaseRole - - BREAKING CHANGE: Fixed so the same user can now be added to a role in one or more databases, and/or one or more instances. Now the parameters `SQLServer` and `SQLInstanceName` are mandatory. - - Enhanced so the same user can now be added to more than one role -- BREAKING CHANGE: Renamed xSQLAlias to xSQLServerAlias to align wíth naming convention. -- Changes to xSQLServerAlwaysOnService - - Added RestartTimeout parameter - - Fixed bug where the SQL Agent service did not get restarted after the IsHadrEnabled property was set. - - BREAKING CHANGE: The mandatory parameters now include Ensure, SQLServer, and SQLInstanceName. SQLServer and SQLInstanceName are keys which will be used to uniquely identify the resource which allows AlwaysOn to be enabled on multiple instances on the same machine. -- Moved Restart-SqlService from MSFT_xSQLServerConfiguration.psm1 to xSQLServerHelper.psm1. -' + - The resource no longer uses Win32_Product WMI class when evaluating if SQL Server Management Studio is installed. See article [kb974524](https://support.microsoft.com/en-us/kb/974524) for more information. + - Now it uses CIM cmdlets to get information from WMI classes. + - Resolved all of the PSScriptAnalyzer warnings that was triggered in the common tests. + - Improvement for service accounts to enable support for Managed Service Accounts as well as other nt authority accounts + - Changes to the helper function Copy-ItemWithRoboCopy + - Robocopy is now started using Start-Process and the error handling has been improved. + - Robocopy now removes files at the destination path if they no longer exists at the source. + - Robocopy copies using unbuffered I/O when available (recommended for large files). + - Added a more descriptive text for the parameter `SourceCredential` to further explain how the parameter work. + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Function `NetUse` has been replaced with `New-SmbMapping` and `Remove-SmbMapping`. + - Renamed function `GetSQLVersion` to `Get-SqlMajorVersion`. + - BREAKING CHANGE: Renamed parameter PID to ProductKey to avoid collision with automatic variable $PID +- Changes to xSQLServerScript + - All credential parameters now also has the type [System.Management.Automation.Credential()] to better work with PowerShell 4.0. + - It is now possible to configure two instances on the same node, with the same script. + - Added to the description text for the parameter `Credential` describing how to authenticate using Windows Authentication. + - Added examples to show how to authenticate using either SQL or Windows authentication. + - A recent issue showed that there is a known problem running this resource using PowerShell 4.0. For more information, see [issue #273](https://github.com/PowerShell/xSQLServer/issues/273) +- Changes to xSQLServerFirewall + - BREAKING CHANGE: Removed parameter SourceFolder. + - BREAKING CHANGE: Removed default value "$PSScriptRoot\..\..\" from parameter SourcePath. + - Old code, that no longer filled any function, has been replaced. + - Function `ResolvePath` has been replaced with `[Environment]::ExpandEnvironmentVariables($SourcePath)` so that environment variables still can be used in Source Path. + - Adding new optional parameter SourceCredential that can be used to authenticate against SourcePath. + - Solved PSSA rules errors in the code. + - Get-TargetResource no longer return $true when no products was installed. +- Changes to the unit test for resource + - xSQLServerSetup + - Added test coverage for helper function Copy-ItemWithRoboCopy +- Changes to xSQLServerLogin + - Removed ShouldProcess statements + - Added the ability to enforce password policies on SQL logins +- Added common test (xSQLServerCommon.Tests) for xSQLServer module + - Now all markdown files will be style checked when tests are running in AppVeyor after sending in a pull request. + - Now all [Examples](/Examples/Resources) will be tested by compiling to a .mof file after sending in a pull request. +- Changes to xSQLServerDatabaseOwner + - The example "SetDatabaseOwner" can now compile, it wrongly had a `DependsOn` in the example. +- Changes to SQLServerRole + - The examples "AddServerRole" and "RemoveServerRole" can now compile, it wrongly had a `DependsOn` in the example. +- Changes to CONTRIBUTING.md + - Added section "Tests for examples files" + - Added section "Tests for style check of Markdown files" + - Added section "Documentation with Markdown" + - Added texts to section "Tests" +- Changes to xSQLServerHelper + - added functions + - Get-SqlDatabaseRecoveryModel + - Set-SqlDatabaseRecoveryModel +- Examples + - xSQLServerDatabaseRecoveryModel + - 1-SetDatabaseRecoveryModel.ps1 + - xSQLServerDatabasePermission + - 1-GrantDatabasePermissions.ps1 + - 2-RevokeDatabasePermissions.ps1 + - 3-DenyDatabasePermissions.ps1 + - xSQLServerFirewall + - 1-CreateInboundFirewallRules + - 2-RemoveInboundFirewallRules +- Added tests for resources + - xSQLServerDatabaseRecoveryModel + - xSQLServerDatabasePermissions + - xSQLServerFirewall +- Changes to xSQLServerDatabaseRecoveryModel + - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include SQLServer, and SQLInstanceName. +- Changes to xSQLServerDatabasePermission + - BREAKING CHANGE: Renamed xSQLServerDatabasePermissions to xSQLServerDatabasePermission to align wíth naming convention. + - BREAKING CHANGE: The mandatory parameters now include PermissionState, SQLServer, and SQLInstanceName. +- Added support for clustered installations to xSQLServerSetup + - Migrated relevant code from xSQLServerFailoverClusterSetup + - Removed Get-WmiObject usage + - Clustered storage mapping now supports asymmetric cluster storage + - Added support for multi-subnet clusters + - Added localized error messages for cluster object mapping + - Updated README.md to reflect new parameters +- Updated description for xSQLServerFailoverClusterSetup to indicate it is deprecated. +- xPDT helper module + - Function GetxPDTVariable was removed since it no longer was used by any resources. + - File xPDT.xml was removed since it was not used by any resources, and did not provide any value to the module. +- Changes xSQLServerHelper moduled + - Removed the globally defined `$VerbosePreference = Continue` from xSQLServerHelper. + - Fixed a typo in a variable name in the function New-ListenerADObject. + - Now Restart-SqlService will correctly show the services it restarts. Also fixed PSSA warnings.' } # End of PSData hashtable @@ -91,3 +160,4 @@ PrivateData = @{ + diff --git a/xSQLServerHelper.psm1 b/xSQLServerHelper.psm1 index 620e21d45..bc5fefb75 100644 --- a/xSQLServerHelper.psm1 +++ b/xSQLServerHelper.psm1 @@ -1,8 +1,5 @@ -# Set Global Module Verbose -$VerbosePreference = 'Continue' - -# Load Localization Data -Import-LocalizedData LocalizedData -filename xSQLServer.strings.psd1 -ErrorAction SilentlyContinue +# Load Localization Data +Import-LocalizedData LocalizedData -filename xSQLServer.strings.psd1 -ErrorAction SilentlyContinue Import-LocalizedData USLocalizedData -filename xSQLServer.strings.psd1 -UICulture en-US -ErrorAction SilentlyContinue <# @@ -12,11 +9,11 @@ Import-LocalizedData USLocalizedData -filename xSQLServer.strings.psd1 -UICultur .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Database Engine instance. #> function Connect-SQL @@ -24,21 +21,21 @@ function Connect-SQL [CmdletBinding()] param ( - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $SQLInstanceName = "MSSQLSERVER", - [ValidateNotNull()] + [ValidateNotNull()] [System.Management.Automation.PSCredential] $SetupCredential ) - + $null = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.Smo') - + if ($SQLInstanceName -eq "MSSQLSERVER") { $connectSql = $SQLServer @@ -47,13 +44,13 @@ function Connect-SQL { $connectSql = "$SQLServer\$SQLInstanceName" } - + if ($SetupCredential) { $sql = New-Object Microsoft.SqlServer.Management.Smo.Server $sql.ConnectionContext.ConnectAsUser = $true $sql.ConnectionContext.ConnectAsUserPassword = $SetupCredential.GetNetworkCredential().Password - $sql.ConnectionContext.ConnectAsUserName = $SetupCredential.GetNetworkCredential().UserName + $sql.ConnectionContext.ConnectAsUserName = $SetupCredential.GetNetworkCredential().UserName $sql.ConnectionContext.ServerInstance = $connectSQL $sql.ConnectionContext.connect() } @@ -79,11 +76,11 @@ function Connect-SQL .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Analysis Service instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Analysis Service instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Analysis Service instance. #> function Connect-SQLAnalysis @@ -91,21 +88,21 @@ function Connect-SQLAnalysis [CmdletBinding()] param ( - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $SQLInstanceName = "MSSQLSERVER", - [ValidateNotNull()] + [ValidateNotNull()] [System.Management.Automation.PSCredential] $SetupCredential ) - + $null = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.AnalysisServices') - + if ($SQLInstanceName -eq "MSSQLSERVER") { $connectSql = $SQLServer @@ -114,12 +111,12 @@ function Connect-SQLAnalysis { $connectSql = "$SQLServer\$SQLInstanceName" } - + $sql = New-Object Microsoft.AnalysisServices.Server if ($SetupCredential) { - $userName = $SetupCredential.GetNetworkCredential().UserName + $userName = $SetupCredential.GetNetworkCredential().UserName $password = $SetupCredential.GetNetworkCredential().Password $sql.Connect("Data Source=$connectSql;User ID=$userName;Password=$password") @@ -151,15 +148,15 @@ function Connect-SQLAnalysis .PARAMETER ErrorCategory The category to use for the error message. Default value is 'OperationStopped'. - Valid values are a value from the enumeration System.Management.Automation.ErrorCategory. + Valid values are a value from the enumeration System.Management.Automation.ErrorCategory. .PARAMETER TargetObject - The object that was being operated on when the error occurred. + The object that was being operated on when the error occurred. .PARAMETER InnerException - Exception object that was thorwn when the error occured, which will be added to the final error message. + Exception object that was thorwn when the error occured, which will be added to the final error message. #> -function New-TerminatingError +function New-TerminatingError { [CmdletBinding()] [OutputType([System.Management.Automation.ErrorRecord])] @@ -177,7 +174,7 @@ function New-TerminatingError [Parameter(Mandatory = $false)] [System.Management.Automation.ErrorCategory] $ErrorCategory = [System.Management.Automation.ErrorCategory]::OperationStopped, - + [Parameter(Mandatory = $false)] [Object] $TargetObject = $null, @@ -186,9 +183,9 @@ function New-TerminatingError [System.Exception] $InnerException = $null ) - + $errorMessage = $LocalizedData.$ErrorType - + if(!$errorMessage) { $errorMessage = ($LocalizedData.NoKeyFound -f $ErrorType) @@ -200,13 +197,13 @@ function New-TerminatingError } $errorMessage = ($errorMessage -f $FormatArgs) - + if( $InnerException ) { $errorMessage += " InnerException: $($InnerException.Message)" } - - $callStack = Get-PSCallStack + + $callStack = Get-PSCallStack # Get Name of calling script if($callStack[1] -and $callStack[1].ScriptName) @@ -214,7 +211,7 @@ function New-TerminatingError $scriptPath = $callStack[1].ScriptName $callingScriptName = $scriptPath.Split('\')[-1].Split('.')[0] - + $errorId = "$callingScriptName.$ErrorType" } else @@ -224,7 +221,7 @@ function New-TerminatingError Write-Verbose -Message "$($USLocalizedData.$ErrorType -f $FormatArgs) | ErrorType: $errorId" - $exception = New-Object System.Exception $errorMessage, $InnerException + $exception = New-Object System.Exception $errorMessage, $InnerException $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $ErrorCategory, $TargetObject return $errorRecord @@ -236,7 +233,7 @@ function New-TerminatingError .PARAMETER WarningType String containing the key of the localized warning message. - + .PARAMETER FormatArgs Collection of strings to replace format objects in warning message. #> @@ -268,7 +265,7 @@ function New-WarningMessage } ## Raise an error indicating the localization data is not present - throw New-TerminatingError @errorParams + throw New-TerminatingError @errorParams } ## Apply formatting @@ -295,7 +292,7 @@ function New-VerboseMessage [Parameter(Mandatory=$true)] $Message ) - Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": $Message"); + Write-Verbose -Message ((Get-Date -format yyyy-MM-dd_HH-mm-ss) + ": $Message") -Verbose } <# @@ -305,27 +302,27 @@ function New-VerboseMessage .PARAMETER CurrentValues This is hashtable of the current values that are applied to the resource. - .PARAMETER DesiredValues + .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 +function Test-SQLDscParameterState { [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [HashTable] $CurrentValues, - - [Parameter(Mandatory = $true)] + + [Parameter(Mandatory = $true)] [Object] $DesiredValues, - [Parameter()] + [Parameter()] [Array] $ValuesToCheck ) @@ -334,66 +331,66 @@ function Test-SQLDscParameterState if (($DesiredValues.GetType().Name -ne "HashTable") ` -and ($DesiredValues.GetType().Name -ne "CimInstance") ` - -and ($DesiredValues.GetType().Name -ne "PSBoundParametersDictionary")) + -and ($DesiredValues.GetType().Name -ne "PSBoundParametersDictionary")) { throw "Property 'DesiredValues' in Test-SQLDscParameterState must be either a " + ` "Hashtable or CimInstance. Type detected was $($DesiredValues.GetType().Name)" } - if (($DesiredValues.GetType().Name -eq "CimInstance") -and ($null -eq $ValuesToCheck)) + if (($DesiredValues.GetType().Name -eq "CimInstance") -and ($null -eq $ValuesToCheck)) { throw "If 'DesiredValues' is a CimInstance then property 'ValuesToCheck' must contain a value" } - if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) + if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) { $keyList = $DesiredValues.Keys - } - else + } + else { $keyList = $ValuesToCheck } $keyList | ForEach-Object -Process { - if (($_ -ne "Verbose")) + if (($_ -ne "Verbose")) { if (($CurrentValues.ContainsKey($_) -eq $false) ` -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` - -or (($DesiredValues.ContainsKey($_) -eq $true) -and ($DesiredValues.$_.GetType().IsArray))) + -or (($DesiredValues.ContainsKey($_) -eq $true) -and ($DesiredValues.$_.GetType().IsArray))) { if ($DesiredValues.GetType().Name -eq "HashTable" -or ` - $DesiredValues.GetType().Name -eq "PSBoundParametersDictionary") + $DesiredValues.GetType().Name -eq "PSBoundParametersDictionary") { - + $checkDesiredValue = $DesiredValues.ContainsKey($_) - } - else + } + else { $checkDesiredValue = Test-SPDSCObjectHasProperty $DesiredValues $_ } - if ($checkDesiredValue) + if ($checkDesiredValue) { $desiredType = $DesiredValues.$_.GetType() $fieldName = $_ - if ($desiredType.IsArray -eq $true) + if ($desiredType.IsArray -eq $true) { if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` - -or ($null -eq $CurrentValues.$fieldName)) + -or ($null -eq $CurrentValues.$fieldName)) { New-VerboseMessage -Message ("Expected to find an array value for " + ` "property $fieldName in the current " + ` "values, but it was either not present or " + ` "was null. This has caused the test method " + ` "to return false.") - + $returnValue = $false - } - else + } + else { $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` -DifferenceObject $DesiredValues.$fieldName - if ($null -ne $arrayCompare) + if ($null -ne $arrayCompare) { New-VerboseMessage -Message ("Found an array for property $fieldName " + ` "in the current values, but this array " + ` @@ -402,14 +399,14 @@ function Test-SQLDscParameterState $arrayCompare | ForEach-Object -Process { New-VerboseMessage -Message "$($_.InputObject) - $($_.SideIndicator)" } - + $returnValue = $false } } - } - else + } + else { - switch ($desiredType.Name) + switch ($desiredType.Name) { "String" { if (-not [String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` @@ -418,46 +415,46 @@ function Test-SQLDscParameterState New-VerboseMessage -Message ("String value for property $fieldName does not match. " + ` "Current state is '$($CurrentValues.$fieldName)' " + ` "and Desired state is '$($DesiredValues.$fieldName)'") - + $returnValue = $false } } "Int32" { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) - { + { New-VerboseMessage -Message ("Int32 value for property " + "$fieldName does not match. " + ` "Current state is " + "'$($CurrentValues.$fieldName)' " + ` "and desired state is " + "'$($DesiredValues.$fieldName)'") - + $returnValue = $false } } "Int16" { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) - { + { New-VerboseMessage -Message ("Int32 value for property " + "$fieldName does not match. " + ` "Current state is " + "'$($CurrentValues.$fieldName)' " + ` "and desired state is " + "'$($DesiredValues.$fieldName)'") - + $returnValue = $false } } default { New-VerboseMessage -Message ("Unable to compare property $fieldName " + ` "as the type ($($desiredType.Name)) is " + ` - "not handled by the Test-SQLDscParameterState cmdlet") - + "not handled by the Test-SQLDscParameterState cmdlet") + $returnValue = $false } } } - } + } } - } + } } - + return $returnValue } @@ -468,11 +465,11 @@ function Test-SQLDscParameterState .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Database Engine instance. .PARAMETER AuthorizedUser @@ -483,25 +480,25 @@ function Grant-ServerPerms [CmdletBinding()] param ( - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLInstanceName= "MSSQLSERVER", - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $SetupCredential, - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $AuthorizedUser ) - + if(!$SQL) { $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName -SetupCredential $SetupCredential @@ -524,20 +521,20 @@ function Grant-ServerPerms .PARAMETER AvailabilityGroupNameListener String containing the name of the Availabilty Group's Virtual Computer Object (VCO). - .PARAMETER CNO - String containing the name of the Cluster Name Object (CNO) for the failover cluster. + .PARAMETER CNO + String containing the name of the Cluster Name Object (CNO) for the failover cluster. #> function Grant-CNOPerms { [CmdletBinding()] Param ( - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $AvailabilityGroupNameListener, - - [ValidateNotNullOrEmpty()] + + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $CNO @@ -551,26 +548,26 @@ function Grant-CNOPerms else{Import-Module ActiveDirectory -ErrorAction Stop -Verbose:$false} Try{ $AG = Get-ADComputer $AvailabilityGroupNameListener - + $comp = $AG.DistinguishedName # input AD computer distinguishedname - $acl = Get-Acl "AD:\$comp" + $acl = Get-Acl "AD:\$comp" $u = Get-ADComputer $CNO # get the AD user object given full control to computer $SID = [System.Security.Principal.SecurityIdentifier] $u.SID - + $identity = [System.Security.Principal.IdentityReference] $SID $adRights = [System.DirectoryServices.ActiveDirectoryRights] "GenericAll" $type = [System.Security.AccessControl.AccessControlType] "Allow" $inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance] "All" $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $identity,$adRights,$type,$inheritanceType - - $acl.AddAccessRule($ace) + + $acl.AddAccessRule($ace) Set-Acl -AclObject $acl "AD:\$comp" New-VerboseMessage -Message "Granted privileges on $comp to $CNO" } Catch{ Throw "Failed to grant Permissions on $comp." Exit - } + } } <# @@ -583,11 +580,11 @@ function Grant-CNOPerms .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. - .PARAMETER SQLInstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance to connect to. .PARAMETER SetupCredential - PSCredential object with the credentials to use to impersonate a user when connecting. + PSCredential object with the credentials to use to impersonate a user when connecting. If this is not provided then the current user will be used to connect to the SQL Server Database Engine instance. #> function New-ListenerADObject @@ -595,20 +592,20 @@ function New-ListenerADObject [CmdletBinding()] Param ( - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.String] $AvailabilityGroupNameListener, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $SQLServer = $env:COMPUTERNAME, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $SQLInstanceName = "MSSQLSERVER", - - [ValidateNotNullOrEmpty()] + + [ValidateNotNullOrEmpty()] [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $SetupCredential @@ -620,7 +617,7 @@ function New-ListenerADObject } $CNO= $SQL.ClusterName - + #Verify Active Directory Tools are installed, if they are load if not Throw Error If (!(Get-Module -ListAvailable | Where-Object {$_.Name -eq "ActiveDirectory"})){ Throw "Active Directory Module is not installed and is Required." @@ -640,10 +637,10 @@ function New-ListenerADObject Throw ": Failed to find Computer in AD" exit } - - + + $m = Get-ADComputer -Filter {Name -eq $AvailabilityGroupNameListener} -Server $env:USERDOMAIN | Select-Object -Property * | Measure-Object - + If ($m.Count -eq 0) { Try{ @@ -655,16 +652,16 @@ function New-ListenerADObject Throw "Failed to Create $AvailabilityGroupNameListener in $OUPath" Exit } - - $SucccessChk =0 - - #Check for AD Object Validate at least three successful attempts + + $SuccessChk =0 + + #Check for AD Object Validate at least three successful attempts $i=1 While ($i -le 5) { Try{ $ListChk = Get-ADComputer -filter {Name -like $AvailabilityGroupNameListener} If ($ListChk){$SuccessChk++} - Start-Sleep -Seconds 10 + Start-Sleep -Seconds 10 If($SuccesChk -eq 3){break} } Catch{ @@ -672,7 +669,7 @@ function New-ListenerADObject Exit } $i++ - } + } } Try{ Grant-CNOPerms -AvailabilityGroupNameListener $AvailabilityGroupNameListener -CNO $CNO @@ -692,21 +689,21 @@ function Import-SQLPSModule { [CmdletBinding()] param() - + <# If SQLPS is not removed between resources (if it was started by another DSC resource) getting objects with the SQL PS provider will fail in some instances because of some sort of inconsistency. Uncertain why this happens. #> if( (Get-Module SQLPS).Count -ne 0 ) { Write-Debug "Unloading SQLPS module." Remove-Module -Name SQLPS -Force -Verbose:$False } - + Write-Debug "SQLPS module changes CWD to SQLSERVER:\ when loading, pushing location to pop it when module is loaded." Push-Location try { New-VerboseMessage -Message "Importing SQLPS module." Import-Module -Name SQLPS -DisableNameChecking -Verbose:$False -ErrorAction Stop # SQLPS has unapproved verbs, disable checking to ignore Warnings. - Write-Debug "SQLPS module imported." + Write-Debug "SQLPS module imported." } catch { throw New-TerminatingError -ErrorType FailedToImportSQLPSModule -ErrorCategory InvalidOperation -InnerException $_.Exception @@ -726,8 +723,8 @@ function Import-SQLPSModule { The SQLPS Provider doesn't use the default instance name of MSSQLSERVER, instead it uses DEFAULT. This function make sure the correct default instance name is returned. - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to validate. + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to validate. #> function Get-SQLPSInstanceName { @@ -741,9 +738,9 @@ function Get-SQLPSInstanceName ) if( $InstanceName -eq "MSSQLSERVER" ) { - $InstanceName = "DEFAULT" + $InstanceName = "DEFAULT" } - + return $InstanceName } @@ -751,11 +748,11 @@ function Get-SQLPSInstanceName .SYNOPSIS Returns the SQL Server SQLPS provider server object. - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to connect to. - .PARAMETER NodeName - String containing the host name of the SQL Server to connect to. + .PARAMETER NodeName + String containing the host name of the SQL Server to connect to. #> function Get-SQLPSInstance { @@ -769,17 +766,17 @@ function Get-SQLPSInstance [Parameter(Mandatory = $true)] [System.String] - $NodeName + $NodeName ) - $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName + $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName $Path = "SQLSERVER:\SQL\$NodeName\$InstanceName" - + New-VerboseMessage -Message "Connecting to $Path as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" Import-SQLPSModule $instance = Get-Item $Path - + return $instance } @@ -787,14 +784,14 @@ function Get-SQLPSInstance .SYNOPSIS Returns the SQL Server SQLPS provider endpoint object. - .PARAMETER Name - String containing the name of the endpoint to return. + .PARAMETER Name + String containing the name of the endpoint to return. - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to connect to. + .PARAMETER InstanceName + String containing the SQL Server Database Engine instance to connect to. - .PARAMETER NodeName - String containing the host name of the SQL Server to connect to. + .PARAMETER NodeName + String containing the host name of the SQL Server to connect to. #> function Get-SQLAlwaysOnEndpoint { @@ -812,21 +809,21 @@ function Get-SQLAlwaysOnEndpoint [Parameter(Mandatory = $true)] [System.String] - $NodeName + $NodeName ) $instance = Get-SQLPSInstance -InstanceName $InstanceName -NodeName $NodeName $Path = "$($instance.PSPath)\Endpoints" Write-Debug "Connecting to $Path as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" - + [String[]] $presentEndpoint = Get-ChildItem $Path if( $presentEndpoint.Count -ne 0 -and $presentEndpoint.Contains("[$Name]") ) { Write-Debug "Connecting to endpoint $Name as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" $endpoint = Get-Item "$Path\$Name" } else { $endpoint = $null - } + } return $endpoint } @@ -835,26 +832,26 @@ function Get-SQLAlwaysOnEndpoint .SYNOPSIS Create a new database in the SQL Server instance provided. - .PARAMETER SQL - An object returned from Connect-SQL function in which the database will be created. + .PARAMETER SQL + An object returned from Connect-SQL function in which the database will be created. - .PARAMETER Name - String containing the database name to be created. + .PARAMETER Name + String containing the database name to be created. #> function New-SqlDatabase { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $SQL, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $Name ) - + $newDatabase = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Database -ArgumentList $SQL,$Name if ($newDatabase) { @@ -864,33 +861,33 @@ function New-SqlDatabase else { New-VerboseMessage -Message "Failed to adding the database $Name" - } + } } <# .SYNOPSIS Remove a database in the SQL Server instance provided. - .PARAMETER SQL - An object returned from Connect-SQL function in which a database will be removed. + .PARAMETER SQL + An object returned from Connect-SQL function in which a database will be removed. - .PARAMETER Name - String containing the database name to be removed. + .PARAMETER Name + String containing the database name to be removed. #> function Remove-SqlDatabase { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $Name ) - + $getDatabase = $Sql.Databases[$Name] if ($getDatabase) { @@ -900,7 +897,7 @@ function Remove-SqlDatabase else { New-VerboseMessage -Message "Failed to deleting the database $Name" - } + } } <# @@ -908,33 +905,33 @@ function Remove-SqlDatabase Add a user to a server role in the SQL Server instance provided. .PARAMETER Sql - An object returned from Connect-SQL function. + An object returned from Connect-SQL function. - .PARAMETER LoginName - String containing the login (user) which should be added as a member to the server role. + .PARAMETER LoginName + String containing the login (user) which should be added as a member to the server role. - .PARAMETER ServerRole - String containing the name of the server role which the user will be added as a member to. + .PARAMETER ServerRole + String containing the name of the server role which the user will be added as a member to. #> function Add-SqlServerRoleMember { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $LoginName, - [ValidateNotNull()] + [ValidateNotNull()] [System.String[]] $ServerRole ) - + $sqlRole = $Sql.Roles if ($sqlRole) { @@ -961,34 +958,34 @@ function Add-SqlServerRoleMember .SYNOPSIS Remove a user in a server role in the SQL Server instance provided. - .PARAMETER Sql - An object returned from Connect-SQL function. + .PARAMETER Sql + An object returned from Connect-SQL function. - .PARAMETER LoginName - String containing the login (user) which should be removed as a member in the server role. + .PARAMETER LoginName + String containing the login (user) which should be removed as a member in the server role. - .PARAMETER ServerRole - String containing the name of the server role for which the user will be removed as a member. + .PARAMETER ServerRole + String containing the name of the server role for which the user will be removed as a member. #> function Remove-SqlServerRoleMember { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $LoginName, - [ValidateNotNull()] + [ValidateNotNull()] [System.String[]] $ServerRole ) - + $sqlRole = $Sql.Roles if ($sqlRole) { @@ -1017,34 +1014,34 @@ function Remove-SqlServerRoleMember The function returns $true is the login (user) is a member in the provided server role. It will return $false if the user is not member of the provided server role. - .PARAMETER SQL - An object returned from Connect-SQL function. + .PARAMETER SQL + An object returned from Connect-SQL function. - .PARAMETER LoginName - String containing the login (user) which should be verified as a member in the server role. + .PARAMETER LoginName + String containing the login (user) which should be verified as a member in the server role. - .PARAMETER ServerRole - String containing the name of the server role which the user will be verified if a member of. + .PARAMETER ServerRole + String containing the name of the server role which the user will be verified if a member of. #> function Confirm-SqlServerRoleMember { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] $Sql, - - [ValidateNotNull()] + + [ValidateNotNull()] [System.String] $LoginName, - [ValidateNotNull()] + [ValidateNotNull()] [System.String[]] $ServerRole ) - + $sqlRole = $Sql.Roles if ($sqlRole) { @@ -1052,7 +1049,7 @@ function Confirm-SqlServerRoleMember { if ($sqlRole[$currentServerRole]) { - $membersInRole = $sqlRole[$currentServerRole].EnumMemberNames() + $membersInRole = $sqlRole[$currentServerRole].EnumMemberNames() if ($membersInRole.Contains($Name)) { $confirmServerRole = $true @@ -1082,30 +1079,30 @@ function Confirm-SqlServerRoleMember <# .SYNOPSIS - This cmdlet is used to return the owner of a SQL database. + This cmdlet is used to return the owner of a SQL database - .PARAMETER SQL - This is an object of the SQL server that contains the result of Connect-SQL. + .PARAMETER Sql + This is an object of the SQL server that contains the result of Connect-SQL .PARAMETER Database - This is the SQL database that will be checking. + This is the SQL database that will be checking #> function Get-SqlDatabaseOwner { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] - $SQL, + $Sql, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $Database ) - + Write-Verbose -Message 'Getting SQL Databases' - $sqlDatabase = $SQL.Databases + $sqlDatabase = $Sql.Databases if ($sqlDatabase) { if ($sqlDatabase[$Database]) @@ -1134,7 +1131,7 @@ function Get-SqlDatabaseOwner .PARAMETER SQL This is an object of the SQL server that contains the result of Connect-SQL. - .PARAMETER Name + .PARAMETER Name This is the name of the desired owner for the SQL database. .PARAMETER Database @@ -1142,25 +1139,25 @@ function Get-SqlDatabaseOwner #> function Set-SqlDatabaseOwner { - [CmdletBinding()] + [CmdletBinding()] param - ( - [ValidateNotNull()] + ( + [ValidateNotNull()] [System.Object] - $SQL, - - [ValidateNotNull()] + $Sql, + + [ValidateNotNull()] [System.String] $Name, - [ValidateNotNull()] + [ValidateNotNull()] [System.String] $Database ) - + Write-Verbose -Message 'Getting SQL Databases' - $sqlDatabase = $SQL.Databases - $sqlLogins = $SQL.Logins + $sqlDatabase = $Sql.Databases + $sqlLogins = $Sql.Logins if ($sqlDatabase -and $sqlLogins) { @@ -1200,7 +1197,7 @@ function Set-SqlDatabaseOwner .PARAMETER SQLServer Hostname of the SQL Server to be configured - + .PARAMETER SQLInstanceName Name of the SQL instance to be configued. Default is 'MSSQLSERVER' @@ -1240,8 +1237,8 @@ function Restart-SqlService if ($serverObject.IsClustered) { ## Get the cluster resources - New-VerboseMessage -Message 'Getting cluster resource for SQL Server' - $sqlService = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Resource -Filter "Type = 'SQL Server'" | + New-VerboseMessage -Message 'Getting cluster resource for SQL Server' + $sqlService = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Resource -Filter "Type = 'SQL Server'" | Where-Object { $_.PrivateProperties.InstanceName -eq $serverObject.ServiceName } New-VerboseMessage -Message 'Getting active cluster resource SQL Server Agent' @@ -1249,10 +1246,10 @@ function Restart-SqlService Where-Object { ($_.Type -eq "SQL Server Agent") -and ($_.State -eq 2) } ## Build a listing of resources being acted upon - $resourceNames = @($sqlService.Name, ($agentService | Select -ExpandProperty Name)) -join "," + $resourceNames = @($sqlService.Name, ($agentService | Select-Object -ExpandProperty Name)) -join "," ## Stop the SQL Server and dependent resources - New-VerboseMessage -Message 'Bringing the SQL Server resources $resourceNames offline.' + New-VerboseMessage -Message "Bringing the SQL Server resources $resourceNames offline." $sqlService | Invoke-CimMethod -MethodName TakeOffline -Arguments @{ Timeout = $Timeout } ## Start the SQL server resource @@ -1286,3 +1283,455 @@ function Restart-SqlService } } } + +<# + .SYNOPSIS + This cmdlet is used to return the recovery model of a SQL database + + .PARAMETER SqlServerObject + This is the SQL Server object returned by Connect-SQL + + .PARAMETER DatabaseName + This is the name of the SQL database +#> +function Get-SqlDatabaseRecoveryModel +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DatabaseName + ) + + Write-Verbose -Message "Getting the recovery model used by the database $DatabaseName" + $sqlDatabase = $SqlServerObject.Databases[$DatabaseName] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + $sqlDatabaseRecoveryModel = $sqlDatabase.RecoveryModel + Write-Verbose -Message "The current recovery model used by database $Name is '$sqlDatabaseRecoveryModel'" + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($DatabaseName,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } + + $sqlDatabaseRecoveryModel +} + +<# + .SYNOPSIS + This cmdlet is used to set the recovery model of a SQL database + + .PARAMETER SqlServerObject + This is the SQL Server object returned by Connect-SQL + + .PARAMETER DatabaseName + This is the name of the SQL database + + .PARAMETER RecoveryModel + The recovery model to set on the databases. Valid values are 'Simple','Full' and 'BulkLogged' +#> +function Set-SqlDatabaseRecoveryModel +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $DatabaseName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Full','Simple','BulkLogged')] + [System.String] + $RecoveryModel + ) + + Write-Verbose -Message "Setting the recovery model for the database $DatabaseName" + $sqlDatabase = $SqlServerObject.Databases[$DatabaseName] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + if($sqlDatabase.RecoveryModel -ne $RecoveryModel) + { + $sqlDatabase.RecoveryModel = $RecoveryModel + $sqlDatabase.Alter() + New-VerboseMessage -Message "The recovery model for the database $DatabaseName is changed to '$RecoveryModel'." + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($DatabaseName,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } +} + +<# + .SYNOPSIS + This cmdlet is used to return the permission for a user in a database + + .PARAMETER SqlServerObject + This is the Server object returned by Connect-SQL + + .PARAMETER Name + This is the name of the user to get the current permissions for + + .PARAMETER Database + This is the name of the SQL database + + .PARAMETER PermissionState + If the permission should be granted or denied. Valid values are Grant or Deny +#> +function Get-SqlDatabasePermission +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Database, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [ValidateNotNullOrEmpty()] + [System.String] + $PermissionState + ) + + Write-Verbose -Message 'Evaluating database and login.' + $sqlDatabase = $SqlServerObject.Databases[$Database] + $sqlLogin = $SqlServerObject.Logins[$Name] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + # Initialize variable permission + [System.String[]] $permission = @() + + if ($sqlDatabase) + { + if ($sqlLogin) + { + Write-Verbose -Message "Getting permissions for user '$Name' in database '$Database'." + + $databasePermissionInfo = $sqlDatabase.EnumDatabasePermissions($Name) + $databasePermissionInfo = $databasePermissionInfo | Where-Object -FilterScript { + $_.PermissionState -eq $PermissionState + } + + foreach ($currentDatabasePermissionInfo in $databasePermissionInfo) + { + $permissionProperty = ($currentDatabasePermissionInfo.PermissionType | Get-Member -MemberType Property).Name + foreach ($currentPermissionProperty in $permissionProperty) + { + if ($currentDatabasePermissionInfo.PermissionType."$currentPermissionProperty") + { + $permission += $currentPermissionProperty + } + } + } + } + else + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } + + $permission +} + +<# + .SYNOPSIS + This cmdlet is used to grant or deny permissions for a user in a database + + .PARAMETER SqlServerObject + This is the Server object returned by Connect-SQL + + .PARAMETER Name + This is the name of the user to get the current permissions for + + .PARAMETER Database + This is the name of the SQL database + + .PARAMETER PermissionState + If the permission should be granted or denied. Valid values are Grant or Deny + + .PARAMETER Permissions + The permissions to be granted or denied for the user in the database. + Valid permissions can be found in the article SQL Server Permissions: + https://msdn.microsoft.com/en-us/library/ms191291.aspx#SQL Server Permissions +#> +function Add-SqlDatabasePermission +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Database, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [ValidateNotNullOrEmpty()] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String[]] + $Permissions + ) + + Write-Verbose -Message 'Evaluating database and login.' + $sqlDatabase = $SqlServerObject.Databases[$Database] + $sqlLogin = $SqlServerObject.Logins[$Name] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + if ($sqlLogin) + { + if (!$sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("Adding SQL login $Name as a user of database " + ` + "$Database on $sqlServer\$sqlInstanceName") + $sqlDatabaseUser = New-Object Microsoft.SqlServer.Management.Smo.User $sqlDatabase,$Name + $sqlDatabaseUser.Login = $Name + $sqlDatabaseUser.Create() + } + catch + { + Write-Verbose -Message ("Failed adding SQL login $Name as a user of " + ` + "database $Database on $sqlServer\$sqlInstanceName") + } + } + + if ($sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("$PermissionState the permissions '$Permissions' to the " + ` + "database '$Database' on the server $sqlServer$sqlInstanceName") + $permissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet + + foreach ($permission in $permissions) + { + $permissionSet."$permission" = $true + } + + switch ($PermissionState) + { + 'Grant' + { + $sqlDatabase.Grant($permissionSet,$Name) + } + + 'Deny' + { + $sqlDatabase.Deny($permissionSet,$Name) + } + } + } + catch + { + Write-Verbose -Message ("Failed setting SQL login $Name to permissions $permissions " + ` + "on database $Database on $sqlServer\$sqlInstanceName") + } + } + } + else + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } +} + +<# + .SYNOPSIS + This cmdlet is used to remove (revoke) permissions for a user in a database + + .PARAMETER SqlServerObject + This is the Server object returned by Connect-SQL. + + .PARAMETER Name + This is the name of the user for which permissions will be removed (revoked) + + .PARAMETER Database + This is the name of the SQL database + + .PARAMETER PermissionState + f the permission that should be removed was granted or denied. Valid values are Grant or Deny + + .PARAMETER Permissions + The permissions to be remove (revoked) for the user in the database. + Valid permissions can be found in the article SQL Server Permissions: + https://msdn.microsoft.com/en-us/library/ms191291.aspx#SQL Server Permissions. +#> +function Remove-SqlDatabasePermission +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Database, + + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny')] + [ValidateNotNullOrEmpty()] + [System.String] + $PermissionState, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String[]] + $Permissions + ) + + Write-Verbose -Message 'Evaluating database and login' + $sqlDatabase = $SqlServerObject.Databases[$Database] + $sqlLogin = $SqlServerObject.Logins[$Name] + $sqlInstanceName = $SqlServerObject.InstanceName + $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + + if ($sqlDatabase) + { + if ($sqlLogin) + { + if (!$sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("Adding SQL login $Name as a user of database " + ` + "$Database on $sqlServer\$sqlInstanceName") + $sqlDatabaseUser = New-Object -TypeName Microsoft.SqlServer.Management.Smo.User ` + -ArgumentList $sqlDatabase,$Name + $sqlDatabaseUser.Login = $Name + $sqlDatabaseUser.Create() + } + catch + { + Write-Verbose -Message ("Failed adding SQL login $Name as a user of " + ` + "database $Database on $sqlServer\$sqlInstanceName") + } + } + + if ($sqlDatabase.Users[$Name]) + { + try + { + Write-Verbose -Message ("Revoking $PermissionState permissions '$Permissions' to the " + ` + "database '$Database' on the server $sqlServer$sqlInstanceName") + $permissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet + + foreach ($permission in $permissions) + { + $permissionSet."$permission" = $false + } + + switch ($PermissionState) + { + 'Grant' + { + $sqlDatabase.Grant($permissionSet,$Name) + } + + 'Deny' + { + $sqlDatabase.Deny($permissionSet,$Name) + } + } + } + catch + { + Write-Verbose -Message ("Failed removing SQL login $Name to permissions $permissions " + ` + "on database $Database on $sqlServer\$sqlInstanceName") + } + } + } + else + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult + } +}