diff --git a/.codecov.yml b/.codecov.yml index 1d2f89209..29a05dd15 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -14,9 +14,8 @@ coverage: status: project: default: - # Set the overall project code coverage requirement to 68 - # This should be 70 - target: 68 + # Set the overall project code coverage requirement to 70% + target: 70 patch: default: # Set the pull request requirement to not regress overall coverage by more than 5% diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 5a8f59e62..0c13825d3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,16 +1,17 @@ - -_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)._ + +**Details of the scenario you tried and the problem that is occurring:** **The DSC configuration that is using the resource (as detailed as possible):** **Version of the Operating System, SQL Server and PowerShell the DSC Target Node is running:** +**What module (SqlServer or SQLPS) and which version of the module the DSC Target Node is running:** + **Version of the DSC module you're using, or 'dev' if you're using current dev branch:** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8556ec240..1aedeca54 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,20 +1,21 @@ -_Thanks for submitting a Pull Request (PR) to this project. Your contribution to this project is greatly appreciated!_ + +**Pull Request (PR) description** [Replace this with a description of your pull request] -This Pull Request (PR) fixes the following issues: +**This Pull Request (PR) fixes the following issues:** [Replace this with the list of issues or n/a. Use format: Fixes #123] +**Task list:** - [ ] 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? diff --git a/CHANGELOG.md b/CHANGELOG.md index a00d757e4..9cbcccb92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,198 @@ ## Unreleased +- Changes to xSQLServerDatabase + - Changed the readme, SQLInstance should have been SQLInstanceName. + +## 7.1.0.0 + +- Changes to xSQLServerMemory + - Changed the way SQLServer parameter is passed from Test-TargetResource to Get-TargetResource so that the default value isn't lost (issue #576). + - Added condition to unit tests for when no SQLServer parameter is set. +- Changes to xSQLServerMaxDop + - Changed the way SQLServer parameter is passed from Test-TargetResource to Get-TargetResource so that the default value isn't lost (issue #576). + - Added condition to unit tests for when no SQLServer parameter is set. +- Changes to xWaitForAvailabilityGroup + - Updated README.md with a description for the resources and revised the parameter descriptions. + - The default value for RetryIntervalSec is now 20 seconds and the default value for RetryCount is now 30 times (issue #505). + - Cleaned up code and fixed PSSA rules warnings (issue #268). + - Added unit tests (issue #297). + - Added descriptive text to README.md that the account that runs the resource must have permission to run the cmdlet Get-ClusterGroup (issue #307). + - Added read-only parameter GroupExist which will return $true if the cluster role/group exist, otherwise it returns $false (issue #510). + - Added examples. +- Changes to xSQLServerPermission + - Cleaned up code, removed SupportsShouldProcess and fixed PSSA rules warnings (issue #241 and issue #262). + - It is now possible to add permissions to two or more logins on the same instance (issue #526). + - The parameter NodeName is no longer mandatory and has now the default value of $env:COMPUTERNAME. + - The parameter Ensure now has a default value of 'Present'. + - Updated README.md with a description for the resources and revised the parameter descriptions. + - Removed dependency of SQLPS provider (issue #482). + - Added ConnectSql permission. Now that permission can also be granted or revoked. + - Updated note in resource description to also mention ConnectSql permission. +- Changes to xSQLServerHelper module + - Removed helper function Get-SQLPSInstance and Get-SQLPSInstanceName because there is no resource using it any longer. + - Added four new helper functions. + - Register-SqlSmo, Register-SqlWmiManagement and Unregister-SqlAssemblies to handle the creation on the application domain and loading and unloading of the SMO and SqlWmiManagement assemblies. + - Get-SqlInstanceMajorVersion to get the major SQL version for a specific instance. + - Fixed typos in comment-based help +- Changes to xSQLServer + - Fixed typos in markdown files; CHANGELOG, CONTRIBUTING, README and ISSUE_TEMPLATE. + - Fixed typos in schema.mof files (and README.md). + - Updated some parameter description in schema.mof files on those that was found was not equal to README.md. +- Changes to xSQLServerAlwaysOnService + - Get-TargetResource should no longer fail silently with error 'Index operation failed; the array index evaluated to null.' (issue #519). Now if the Server.IsHadrEnabled property return neither $true or $false the Get-TargetResource function will throw an error. +- Changes to xSQLServerSetUp + - Updated xSQLServerSetup Module Get-Resource method to fix (issue #516 and #490). + - Added change to detect DQ, DQC, BOL, SDK features. Now the function Test-TargetResource returns true after calling set for DQ, DQC, BOL, SDK features (issue #516 and #490). +- Changes to xSQLServerAlwaysOnAvailabilityGroup + - Updated to return the exception raised when an error is thrown. +- Changes to xSQLServerAlwaysOnAvailabilityGroupReplica + - Updated to return the exception raised when an error is thrown. + - Updated parameter description for parameter Name, so that it says it must be in the format SQLServer\InstanceName for named instance (issue #548). +- Changes to xSQLServerLogin + - Added an optional boolean parameter Disabled. It can be used to enable/disable existing logins or create disabled logins (new logins are created as enabled by default). +- Changes to xSQLServerDatabaseRole + - Updated variable passed to Microsoft.SqlServer.Management.Smo.User constructor to fix issue #530 +- Changes to xSQLServerNetwork + - Added optional parameter SQLServer with default value of $env:COMPUTERNAME (issue #528). + - Added optional parameter RestartTimeout with default value of 120 seconds. + - Now the resource supports restarting a sql server in a cluster (issue #527 and issue #455). + - Now the resource allows to set the parameter TcpDynamicPorts to a blank value (partly fixes issue #534). Setting a blank value for parameter TcpDynamicPorts together with a value for parameter TcpPort means that static port will be used. + - Now the resource will not call Alter() in the Set-TargetResource when there is no change necessary (issue #537). + - Updated example 1-EnableTcpIpOnCustomStaticPort. + - Added unit tests (issue #294). + - Refactored some of the code, cleaned up the rest and fixed PSSA rules warnings (issue #261). + - If parameter TcpDynamicPort is set to '0' at the same time as TcpPort is set the resource will now throw an error (issue #535). + - Added examples (issue #536). + - When TcpDynamicPorts is set to '0' the Test-TargetResource function will no longer fail each time (issue #564). +- Changes to xSQLServerRSConfig + - Replaced sqlcmd.exe usages with Invoke-Sqlcmd calls (issue #567). +- Changes to xSQLServerDatabasePermission + - Fixed code style, updated README.md and removed *-SqlDatabasePermission functions from xSQLServerHelper.psm1. + - Added the option 'GrantWithGrant' with gives the user grant rights, together with the ability to grant others the same right. + - Now the resource can revoke permission correctly (issue #454). When revoking 'GrantWithGrant', both the grantee and all the other users the grantee has granted the same permission to, will also get their permission revoked. + - Updated tests to cover Revoke(). +- Changes to xSQLServerHelper + - The missing helper function ('Test-SPDSCObjectHasProperty'), that was referenced in the helper function Test-SQLDscParameterState, is now incorporated into Test-SQLDscParameterState (issue #589). + +## 7.0.0.0 + +- Examples + - xSQLServerDatabaseRole + - 1-AddDatabaseRole.ps1 + - 2-RemoveDatabaseRole.ps1 + - xSQLServerRole + - 3-AddMembersToServerRole.ps1 + - 4-MembersToIncludeInServerRole.ps1 + - 5-MembersToExcludeInServerRole.ps1 + - xSQLServerSetup + - 1-InstallDefaultInstanceSingleServer.ps1 + - 2-InstallNamedInstanceSingleServer.ps1 + - 3-InstallNamedInstanceSingleServerFromUncPathUsingSourceCredential.ps1 + - 4-InstallNamedInstanceInFailoverClusterFirstNode.ps1 + - 5-InstallNamedInstanceInFailoverClusterSecondNode.ps1 + - xSQLServerReplication + - 1-ConfigureInstanceAsDistributor.ps1 + - 2-ConfigureInstanceAsPublisher.ps1 + - xSQLServerNetwork + - 1-EnableTcpIpOnCustomStaticPort.ps1 + - xSQLServerAvailabilityGroupListener + - 1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 + - 2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 + - 3-RemoveAvailabilityGroupListenerWithSameNameAsVCO.ps1 + - 4-RemoveAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 + - 5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 + - 6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 + - xSQLServerEndpointPermission + - 1-AddConnectPermission.ps1 + - 2-RemoveConnectPermission.ps1 + - 3-AddConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 + - 4-RemoveConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 + - xSQLServerPermission + - 1-AddServerPermissionForLogin.ps1 + - 2-RemoveServerPermissionForLogin.ps1 + - xSQLServerEndpointState + - 1-MakeSureEndpointIsStarted.ps1 + - 2-MakeSureEndpointIsStopped.ps1 + - xSQLServerConfiguration + - 1-ConfigureTwoInstancesOnTheSameServerToEnableClr.ps1 + - 2-ConfigureInstanceToEnablePriorityBoost.ps1 + - xSQLServerEndpoint + - 1-CreateEndpointWithDefaultValues.ps1 + - 2-CreateEndpointWithSpecificPortAndIPAddress.ps1 + - 3-RemoveEndpoint.ps1 +- Changes to xSQLServerDatabaseRole + - Fixed code style, added updated parameter descriptions to schema.mof and README.md. +- Changes to xSQLServer + - Raised the CodeCov target to 70% which is the minimum and required target for HQRM resource. +- Changes to xSQLServerRole + - **BREAKING CHANGE: The resource has been reworked in it's entirely.** Below is what has changed. + - The mandatory parameters now also include ServerRoleName. + - The ServerRole parameter was before an array of server roles, now this parameter is renamed to ServerRoleName and can only be set to one server role. + - ServerRoleName are no longer limited to built-in server roles. To add members to a built-in server role, set ServerRoleName to the name of the built-in server role. + - The ServerRoleName will be created when Ensure is set to 'Present' (if it does not already exist), or removed if Ensure is set to 'Absent'. + - Three new parameters are added; Members, MembersToInclude and MembersToExclude. + - Members can be set to one or more logins, and those will _replace all_ the memberships in the server role. + - MembersToInclude and MembersToExclude can be set to one or more logins that will add or remove memberships, respectively, in the server role. MembersToInclude and MembersToExclude _can not_ be used at the same time as parameter Members. But both MembersToInclude and MembersToExclude can be used together at the same time. +- Changes to xSQLServerSetup + - Added a note to the README.md saying that it is not possible to add or remove features from a SQL Server failover cluster (issue #433). + - Changed so that it reports false if the desired state is not correct (issue #432). + - Added a test to make sure we always return false if a SQL Server failover cluster is missing features. + - Helper function Connect-SQLAnalysis + - Now has correct error handling, and throw does not used the unknown named parameter '-Message' (issue #436) + - Added tests for Connect-SQLAnalysis + - Changed to localized error messages. + - Minor changes to error handling. + - This adds better support for Addnode (issue #369). + - Now it skips cluster validation för add node (issue #442). + - Now it ignores parameters that are not allowed for action Addnode (issue #441). + - Added support for vNext CTP 1.4 (issue #472). +- Added new resource + - xSQLServerAlwaysOnAvailabilityGroupReplica +- Changes to xSQLServerDatabaseRecoveryModel + - Fixed code style, removed SQLServerDatabaseRecoveryModel functions from xSQLServerHelper. +- Changes to xSQLServerAlwaysOnAvailabilityGroup + - Fixed the permissions check loop so that it exits the loop after the function determines the required permissions are in place. +- Changes to xSQLServerAvailabilityGroupListener + - Removed the dependency of SQLPS provider (issue #460). + - Cleaned up code. + - Added test for more coverage. + - Fixed PSSA rule warnings (issue #255). + - Parameter Ensure now defaults to 'Present' (issue #450). +- Changes to xSQLServerFirewall + - Now it will correctly create rules when the resource is used for two or more instances on the same server (issue #461). +- Changes to xSQLServerEndpointPermission + - Added description to the README.md + - Cleaned up code (issue #257 and issue #231) + - Now the default value for Ensure is 'Present'. + - Removed dependency of SQLPS provider (issue #483). + - Refactored tests so they use less code. +- Changes to README.md + - Adding deprecated tag to xSQLServerFailoverClusterSetup, xSQLAOGroupEnsure and xSQLAOGroupJoin in README.md so it it more clear that these resources has been replaced by xSQLServerSetup, xSQLServerAlwaysOnAvailabilityGroup and xSQLServerAlwaysOnAvailabilityGroupReplica respectively. +- Changes to xSQLServerEndpoint + - BREAKING CHANGE: Now SQLInstanceName is mandatory, and is a key, so SQLInstanceName has no longer a default value (issue #279). + - BREAKING CHANGE: Parameter AuthorizedUser has been removed (issue #466, issue #275 and issue #80). Connect permissions can be set using the resource xSQLServerEndpointPermission. + - Optional parameter IpAddress has been added. Default is to listen on any valid IP-address. (issue #232) + - Parameter Port now has a default value of 5022. + - Parameter Ensure now defaults to 'Present'. + - Resource now supports changing IP address and changing port. + - Added unit tests (issue #289) + - Added examples. +- Changes to xSQLServerEndpointState + - Cleaned up code, removed SupportsShouldProcess and fixed PSSA rules warnings (issue #258 and issue #230). + - Now the default value for the parameter State is 'Started'. + - Updated README.md with a description for the resources and revised the parameter descriptions. + - Removed dependency of SQLPS provider (issue #481). + - The parameter NodeName is no longer mandatory and has now the default value of $env:COMPUTERNAME. + - The parameter Name is now a key so it is now possible to change the state on more than one endpoint on the same instance. _Note: The resource still only supports Database Mirror endpoints at this time._ +- Changes to xSQLServerHelper module + - Removing helper function Get-SQLAlwaysOnEndpoint because there is no resource using it any longer. + - BREAKING CHANGE: Changed helper function Import-SQLPSModule to support SqlServer module (issue #91). The SqlServer module is the preferred module so if it is found it will be used, and if not found an attempt will be done to load SQLPS module instead. +- Changes to xSQLServerScript + - Updated tests for this resource, because they failed when Import-SQLPSModule was updated. + +## 6.0.0.0 + - Changes to xSQLServerConfiguration - BREAKING CHANGE: The parameter SQLInstanceName is now mandatory. - Resource can now be used to define the configuration of two or more different DB instances on the same server. @@ -15,8 +207,10 @@ - It now detects that feature Client Connectivity Tools (CONN) and Client Connectivity Backwards Compatibility Tools (BC) is installed. - Now it can correctly determine the right cluster when only parameter InstallSQLDataDir is assigned a path (issue #401). - Now the only mandatory path parameter is InstallSQLDataDir when installing Database Engine (issue #400). - - It now can handle mandatory parameters, and are not using wildcards to find the variables containing paths (issue #394). - - Changed to that instead of connection to localhost it is using $env:COMPUTERNAME as the host name to which it connects. And for cluster installation it uses the parameter FailoverClusterNetworkName as the host name to which it connects (issue #407). + - It now can handle mandatory parameters, and are not using wildcard to find the variables containing paths (issue #394). + - Changed so that instead of connection to localhost it is using $env:COMPUTERNAME as the host name to which it connects. And for cluster installation it uses the parameter FailoverClusterNetworkName as the host name to which it connects (issue #407). + - When called with Action = 'PrepareFailoverCluster', the SQLSysAdminAccounts and FailoverClusterGroup parameters are no longer passed to the setup process (issues #410 and 411). + - Solved the problem that InstanceDir and InstallSQLDataDir could not be set to just a qualifier, i.e 'E:' (issue #418). All paths (except SourcePath) can now be set to just the qualifier. - Enables CodeCov.io code coverage reporting. - Added badge for CodeCov.io to README.md. - Examples @@ -58,7 +252,7 @@ - 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. + - Added more detailed text explaining what operating systems 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 @@ -129,10 +323,10 @@ - xSQLServerDatabasePermissions - xSQLServerFirewall - Changes to xSQLServerDatabaseRecoveryModel - - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align wíth naming convention. + - BREAKING CHANGE: Renamed xSQLDatabaseRecoveryModel to xSQLServerDatabaseRecoveryModel to align with 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: Renamed xSQLServerDatabasePermissions to xSQLServerDatabasePermission to align with 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 @@ -145,7 +339,7 @@ - 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 +- Changes xSQLServerHelper module - 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. @@ -181,7 +375,7 @@ - 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. +- BREAKING CHANGE: Renamed xSQLAlias to xSQLServerAlias to align with 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. @@ -323,7 +517,7 @@ - Removed default values for parameters, to avoid compatibility issues and setup errors - Added Replication sub feature detection - Added setup parameter BrowserSvcStartupType - - Change SourceFolder to Source to allow for multiversion Support + - Change SourceFolder to Source to allow for multi version Support - Add Source Credential for accessing source files - Add Parameters for SQL Server configuration - Add Parameters to SuppressReboot or ForceReboot @@ -331,7 +525,7 @@ - Removed default values for parameters, to avoid compatibility issues - Updated firewall rule name to not use 2012 version, since package supports 2008, 2012 and 2014 versions - Additional of SQLHelper Function and error handling - - Change SourceFolder to Source to allow for multiversion Support + - Change SourceFolder to Source to allow for multi version Support - xSQLServerNetwork - Added new resource that configures network settings. - Currently supports only tcp network protocol @@ -342,7 +536,7 @@ - xSqlServerRSConfig - xSQLServerFailoverClusterSetup - Additional of SQLHelper Function and error handling - - Change SourceFolder to Source to allow for multiversion Support + - Change SourceFolder to Source to allow for multi version Support - Add Parameters to SuppressReboot or ForceReboot - Examples - Updated example files to use correct DebugMode parameter value ForceModuleImport, this is not boolean in WMF 5.0 RTM @@ -350,7 +544,7 @@ ## 1.3.0.0 -- xSqlServerSetus +- xSqlServerSetup - Make Features case-insensitive. ## 1.2.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 358c405df..26a201a0d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ If you are keen to make xSQLServer better, why not consider contributing your wo ## Core contribution guidelines -We follow all of the standard contribution guidelines for DSC resources [outlined in DscResources repo](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md), so please review these as a baseline for contributing. +We follow all of the standard contribution guidelines for DSC resources [outlined in DscResources repository](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md), so please review these as a baseline for contributing. ## xSQLServer specific guidelines @@ -12,9 +12,9 @@ We follow all of the standard contribution guidelines for DSC resources [outline Any resource should be able to target at least all SQL Server versions that are currently supported by Microsoft (also those in extended support). Unless the functionality that the resource targets does not exist in a certain SQL Server version. -There can also be other limitations that restrict the resource from tageting all supported versions. +There can also be other limitations that restrict the resource from targeting all supported versions. -Those SQL Server products that are still supported can be listed at the [Microsoft lifecycle site](https://support.microsoft.com/en-us/lifecycle/search?alpha=SQL%20Server). +Those SQL Server products that are still supported can be listed at the [Microsoft life cycle site](https://support.microsoft.com/en-us/lifecycle/search?alpha=SQL%20Server). ### Naming convention diff --git a/DSCResources/MSFT_xSQLAOGroupEnsure/MSFT_xSQLAOGroupEnsure.schema.mof b/DSCResources/MSFT_xSQLAOGroupEnsure/MSFT_xSQLAOGroupEnsure.schema.mof index 0d722dd6b..51ee7a795 100644 --- a/DSCResources/MSFT_xSQLAOGroupEnsure/MSFT_xSQLAOGroupEnsure.schema.mof +++ b/DSCResources/MSFT_xSQLAOGroupEnsure/MSFT_xSQLAOGroupEnsure.schema.mof @@ -5,11 +5,11 @@ class MSFT_xSQLAOGroupEnsure : OMI_BaseResource [Key, Description("Name for availability group.")] String AvailabilityGroupName; [Write, Description("Listener name for availability group.")] String AvailabilityGroupNameListener; [Write, Description("List of IP addresses associated with listener.")] String AvailabilityGroupNameIP[]; - [Write, Description("Network subnetmask for listener.")] String AvailabilityGroupSubMask[]; + [Write, Description("Network subnet mask for listener.")] String AvailabilityGroupSubMask[]; [Write, Description("Port availability group should listen on.")] Uint32 AvailabilityGroupPort; [Write, Description("Mode secondaries should operate under (None, ReadOnly, ReadIntent)."), ValueMap{"None","ReadOnly","ReadIntent"}, Values{"None","ReadOnly","ReadIntent"}] String ReadableSecondary; [Write, Description("Where backups should be backed up from (Primary, Secondary)."), ValueMap{"Primary","Secondary"}, Values{"Primary","Secondary"}] String AutoBackupPreference; - [Write, Description("The percentage weight for backup prority (default 50).")] Uint32 BackupPriority; + [Write, Description("The percentage weight for backup priority (default 50).")] Uint32 BackupPriority; [Write, Description("he TCP port for the SQL AG Endpoint (default 5022).")] Uint32 EndPointPort; [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_xSQLServerAlias/MSFT_xSQLServerAlias.schema.mof b/DSCResources/MSFT_xSQLServerAlias/MSFT_xSQLServerAlias.schema.mof index bf613ea9f..241daec8e 100644 --- a/DSCResources/MSFT_xSQLServerAlias/MSFT_xSQLServerAlias.schema.mof +++ b/DSCResources/MSFT_xSQLServerAlias/MSFT_xSQLServerAlias.schema.mof @@ -3,7 +3,7 @@ class MSFT_xSQLServerAlias : OMI_BaseResource { [Key, Description("The name of Alias (e.g. svr01\\inst01).")] String Name; [Write, Description("Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes). Default value is 'TCP'."), ValueMap{"TCP","NP"}, Values{"TCP","NP"}] String Protocol; - [Key, Description("The SQL Server you are aliasing (the netbios name or FQDN).")] String ServerName; + [Key, Description("The SQL Server you are aliasing (the NetBIOS name or FQDN).")] String ServerName; [Write, Description("The TCP port SQL is listening on. Only used when protocol is set to 'TCP'. Default value is port 1433.")] UInt16 TcpPort; [Write, Description("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'.")] Boolean UseDynamicTcpPort; [Read, Description("Named Pipes path from the Get-TargetResource method.")] String PipeName; diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.psm1 b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.psm1 index 4536ea22f..955927b59 100644 --- a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.psm1 +++ b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.psm1 @@ -240,7 +240,7 @@ function Set-TargetResource } catch { - throw New-TerminatingError -ErrorType RemoveAvailabilityGroupFailed -FormatArgs $availabilityGroup.Name,$SQLInstanceName -ErrorCategory ResourceUnavailable + throw New-TerminatingError -ErrorType RemoveAvailabilityGroupFailed -FormatArgs $availabilityGroup.Name,$SQLInstanceName -ErrorCategory ResourceUnavailable -InnerException $_.Exception } } else @@ -254,55 +254,55 @@ function Set-TargetResource { $clusterServiceName = 'NT SERVICE\ClusSvc' $ntAuthoritySystemName = 'NT AUTHORITY\SYSTEM' - $availabilityGroupManagementRoleName = 'AG_Management' $availabilityGroupManagementPerms = @('Connect SQL','Alter Any Availability Group','View Server State') $clusterPermissionsPresent = $false - $permissionsParams = @{ - SQLServer = $SQLServer - SQLInstanceName = $SQLInstanceName - Database = 'master' - WithResults = $true - } - foreach ( $loginName in @( $clusterServiceName, $ntAuthoritySystemName ) ) { - if ( $serverObject.Logins[$loginName] -and -not $clusterPermissionsPresent ) + if ( $serverObject.Logins[$loginName] ) { - $queryToGetEffectivePermissionsForLogin = " - EXECUTE AS LOGIN = '$loginName' - SELECT DISTINCT permission_name - FROM fn_my_permissions(null,'SERVER') - REVERT - " - - $loginEffectivePermissionsResult = Invoke-Query @permissionsParams -Query $queryToGetEffectivePermissionsForLogin - $loginEffectivePermissions = $loginEffectivePermissionsResult.Tables.Rows.permission_name + $testLoginEffectivePermissionsParams = @{ + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + LoginName = $loginName + Permissions = $availabilityGroupManagementPerms + } + + $clusterPermissionsPresent = Test-LoginEffectivePermissions @testLoginEffectivePermissionsParams + + if ( $clusterPermissionsPresent ) + { + # Exit the loop when the script verifies the required cluster permissions are present + break + } + else + { + switch ( $loginName ) + { + $clusterServiceName + { + New-VerboseMessage -Message "The recommended account '$loginName' is missing one or more of the following permissions: $( $availabilityGroupManagementPerms -join ', ' ). Trying with '$ntAuthoritySystemName'." + } - if ( $null -ne $loginEffectivePermissions ) + $ntAuthoritySystemName + { + New-VerboseMessage -Message "'$loginName' is missing one or more of the following permissions: $( $availabilityGroupManagementPerms -join ', ' )" + } + } + } + } + else + { + switch ( $loginName ) { - $loginMissingPermissions = Compare-Object -ReferenceObject $availabilityGroupManagementPerms -DifferenceObject $loginEffectivePermissions | - Where-Object { $_.SideIndicator -ne '=>' } | - Select-Object -ExpandProperty InputObject - - if ( $loginMissingPermissions.Count -eq 0 ) + $clusterServiceName { - $clusterPermissionsPresent = $true + New-VerboseMessage -Message "The recommended login '$loginName' is not present. Trying with '$ntAuthoritySystemName'." } - else + + $ntAuthoritySystemName { - switch ( $loginName ) - { - $clusterServiceName - { - New-VerboseMessage -Message "The recommended account '$loginName' is missing the following permissions: $( $loginMissingPermissions -join ', ' ). Trying with '$ntAuthoritySystemName'." - } - - $ntAuthoritySystemName - { - New-VerboseMessage -Message "'$loginName' is missing the following permissions: $( $loginMissingPermissions -join ', ' )" - } - } + New-VerboseMessage -Message "The login '$loginName' is not present." } } } @@ -361,7 +361,7 @@ function Set-TargetResource } catch { - throw New-TerminatingError -ErrorType CreateAvailabilityGroupReplicaFailed -FormatArgs $Ensure,$SQLInstanceName -ErrorCategory OperationStopped + throw New-TerminatingError -ErrorType CreateAvailabilityGroupReplicaFailed -FormatArgs $newReplicaParams.Name,$SQLInstanceName -ErrorCategory OperationStopped -InnerException $_.Exception } # Set up the parameters for the new availability group @@ -398,7 +398,7 @@ function Set-TargetResource } catch { - throw New-TerminatingError -ErrorType CreateAvailabilityGroupFailed -FormatArgs $Name,$_.Exception -ErrorCategory OperationStopped + throw New-TerminatingError -ErrorType CreateAvailabilityGroupFailed -FormatArgs $Name -ErrorCategory OperationStopped -InnerException $_.Exception } } # Otherwise let's check each of the parameters passed and update the Availability Group accordingly @@ -747,39 +747,7 @@ function Update-AvailabilityGroup } catch { - throw New-TerminatingError -ErrorType AlterAvailabilityGroupFailed -FormatArgs $AvailabilityGroup.Name -ErrorCategory OperationStopped - } - finally - { - $ErrorActionPreference = $originalErrorActionPreference - } -} - -<# - .SYNOPSIS - Executes the alter method on an Availability Group Replica object. - - .PARAMETER AvailabilityGroupReplica - The Availabilty Group Replica object that must be altered. -#> -function Update-AvailabilityGroupReplica -{ - param - ( - [Parameter(Mandatory = $true)] - [Microsoft.SqlServer.Management.Smo.AvailabilityReplica] - $AvailabilityGroupReplica - ) - - try - { - $originalErrorActionPreference = $ErrorActionPreference - $ErrorActionPreference = 'Stop' - $AvailabilityGroupReplica.Alter() - } - catch - { - throw New-TerminatingError -ErrorType AlterAvailabilityGroupReplicaFailed -FormatArgs $AvailabilityGroupReplica.Name -ErrorCategory OperationStopped + throw New-TerminatingError -ErrorType AlterAvailabilityGroupFailed -FormatArgs $AvailabilityGroup.Name -ErrorCategory OperationStopped -InnerException $_.Exception } finally { diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.schema.mof b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.schema.mof index 07947f4d4..8a3ecd9d5 100644 --- a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.schema.mof +++ b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroup/MSFT_xSQLServerAlwaysOnAvailabilityGroup.schema.mof @@ -3,7 +3,7 @@ class MSFT_xSQLServerAlwaysOnAvailabilityGroup : OMI_BaseResource { [Key, Description("The name of the availability group.")] String Name; [Required, Description("Hostname of the SQL Server to be configured.")] String SQLServer; - [Key, Description("Name of the SQL instance to be configued.")] String SQLInstanceName; + [Key, Description("Name of the SQL instance to be configured.")] String SQLInstanceName; [Write, Description("Specifies if the availability group should be present or absent. Default is Present."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Write, Description("Specifies the automated backup preference for the availability group. Default is None"), ValueMap{"Primary","SecondaryOnly","Secondary","None"}, Values{"Primary","SecondaryOnly","Secondary","None"}] String AutomatedBackupPreference; [Write, Description("Specifies the replica availability mode. Default is 'AsynchronousCommit'."), ValueMap{"AsynchronousCommit","SynchronousCommit"}, Values{"AsynchronousCommit","SynchronousCommit"}] String AvailabilityMode; diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.psm1 b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.psm1 new file mode 100644 index 000000000..0aad850a5 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.psm1 @@ -0,0 +1,738 @@ +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force + +<# + .SYNOPSIS + Gets the specified Availabilty Group Replica from the specified Availabilty Group. + + .PARAMETER Name + The name of the availability group replica. + + .PARAMETER AvailabilityGroupName + The name of the availability group. + + .PARAMETER SQLServer + Hostname of the SQL Server to be configured. + + .PARAMETER SQLInstanceName + Name of the SQL instance to be configued. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [String] + $Name, + + [Parameter(Mandatory = $true)] + [String] + $AvailabilityGroupName, + + [Parameter(Mandatory = $true)] + [String] + $SQLServer, + + [Parameter(Mandatory = $true)] + [String] + $SQLInstanceName + ) + + # Connect to the instance + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + # Get the endpoint properties + $endpoint = $serverObject.Endpoints | Where-Object { $_.EndpointType -eq 'DatabaseMirroring' } + if ( $endpoint ) + { + $endpointPort = $endpoint.Protocol.Tcp.ListenerPort + } + + # Create the return object + $alwaysOnAvailabilityGroupReplicaResource = @{ + Ensure = 'Absent' + Name = '' + AvailabilityGroupName = '' + AvailabilityMode = '' + BackupPriority = '' + ConnectionModeInPrimaryRole = '' + ConnectionModeInSecondaryRole = '' + FailoverMode = '' + EndpointUrl = '' + ReadOnlyRoutingConnectionUrl = '' + ReadOnlyRoutingList = @() + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + EndpointPort = $endpointPort + SQLServerNetName = $serverObject.NetName + } + + # Get the availability group + $availabilityGroup = $serverObject.AvailabilityGroups[$AvailabilityGroupName] + + if ( $availabilityGroup ) + { + # Add the Availability Group name to the results + $alwaysOnAvailabilityGroupReplicaResource.AvailabilityGroupName = $availabilityGroup.Name + + # Try to find the replica + $availabilityGroupReplica = $availabilityGroup.AvailabilityReplicas[$Name] + + if ( $availabilityGroupReplica ) + { + # Add the Availability Group Replica properties to the results + $alwaysOnAvailabilityGroupReplicaResource.Ensure = 'Present' + $alwaysOnAvailabilityGroupReplicaResource.Name = $availabilityGroupReplica.Name + $alwaysOnAvailabilityGroupReplicaResource.AvailabilityMode = $availabilityGroupReplica.AvailabilityMode + $alwaysOnAvailabilityGroupReplicaResource.BackupPriority = $availabilityGroupReplica.BackupPriority + $alwaysOnAvailabilityGroupReplicaResource.ConnectionModeInPrimaryRole = $availabilityGroupReplica.ConnectionModeInPrimaryRole + $alwaysOnAvailabilityGroupReplicaResource.ConnectionModeInSecondaryRole = $availabilityGroupReplica.ConnectionModeInSecondaryRole + $alwaysOnAvailabilityGroupReplicaResource.FailoverMode = $availabilityGroupReplica.FailoverMode + $alwaysOnAvailabilityGroupReplicaResource.EndpointUrl = $availabilityGroupReplica.EndpointUrl + $alwaysOnAvailabilityGroupReplicaResource.ReadOnlyRoutingConnectionUrl = $availabilityGroupReplica.ReadOnlyRoutingConnectionUrl + $alwaysOnAvailabilityGroupReplicaResource.ReadOnlyRoutingList = $availabilityGroupReplica.ReadOnlyRoutingList + } + } + + return $alwaysOnAvailabilityGroupReplicaResource +} + +<# + .SYNOPSIS + Creates or removes the availability group replica in accordance with the desired state. + + .PARAMETER Name + The name of the availability group replica. + + .PARAMETER AvailabilityGroupName + The name of the availability group. + + .PARAMETER SQLServer + Hostname of the SQL Server to be configured. + + .PARAMETER SQLInstanceName + Name of the SQL instance to be configued. + + .PARAMETER PrimaryReplicaSQLServer + Hostname of the SQL Server where the primary replica is expected to be active. If the primary replica is not found here, the resource will attempt to find the host that holds the primary replica and connect to it. + + .PARAMETER PrimaryReplicaSQLInstanceName + Name of the SQL instance where the primary replica lives. + + .PARAMETER Ensure + Specifies if the availability group should be present or absent. Default is Present. + + .PARAMETER AvailabilityMode + Specifies the replica availability mode. Default is 'AsynchronousCommit'. + + .PARAMETER BackupPriority + Specifies the desired priority of the replicas in performing backups. The acceptable values for this parameter are integers from 0 through 100. Of the set of replicas which are online and available, the replica that has the highest priority performs the backup. Default is 50. + + .PARAMETER ConnectionModeInPrimaryRole + Specifies how the availability replica handles connections when in the primary role. + + .PARAMETER ConnectionModeInSecondaryRole + Specifies how the availability replica handles connections when in the secondary role. + + .PARAMETER EndpointHostName + Specifies the hostname or IP address of the availability group replica endpoint. Default is the instance network name which is set in the code because the value can only be determined when connected to the SQL Instance. + + .PARAMETER FailoverMode + Specifies the failover mode. Default is Manual. + + .PARAMETER ReadOnlyRoutingConnectionUrl + Specifies the fully-qualified domain name (FQDN) and port to use when routing to the replica for read only connections. + + .PARAMETER ReadOnlyRoutingList + Specifies an ordered list of replica server names that represent the probe sequence for connection director to use when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group. +#> +function Set-TargetResource +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory = $true)] + [String] + $Name, + + [Parameter(Mandatory = $true)] + [String] + $AvailabilityGroupName, + + [Parameter(Mandatory = $true)] + [String] + $SQLServer, + + [Parameter(Mandatory = $true)] + [String] + $SQLInstanceName, + + [Parameter()] + [String] + $PrimaryReplicaSQLServer, + + [Parameter()] + [String] + $PrimaryReplicaSQLInstanceName, + + [Parameter()] + [ValidateSet('Present','Absent')] + [String] + $Ensure = 'Present', + + [Parameter()] + [ValidateSet('AsynchronousCommit','SynchronousCommit')] + [String] + $AvailabilityMode = 'AsynchronousCommit', + + [Parameter()] + [ValidateRange(0,100)] + [UInt32] + $BackupPriority = 50, + + [Parameter()] + [ValidateSet('AllowAllConnections','AllowReadWriteConnections')] + [String] + $ConnectionModeInPrimaryRole, + + [Parameter()] + [ValidateSet('AllowNoConnections','AllowReadIntentConnectionsOnly','AllowAllConnections')] + [String] + $ConnectionModeInSecondaryRole, + + [Parameter()] + [String] + $EndpointHostName, + + [Parameter()] + [ValidateSet('Automatic','Manual')] + [String] + $FailoverMode = 'Manual', + + [Parameter()] + [String] + $ReadOnlyRoutingConnectionUrl, + + [Parameter()] + [String[]] + $ReadOnlyRoutingList + ) + + Import-SQLPSModule + + # Connect to the instance + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + # Determine if HADR is enabled on the instance. If not, throw an error + if ( -not $serverObject.IsHadrEnabled ) + { + throw New-TerminatingError -ErrorType HadrNotEnabled -FormatArgs $Ensure,$SQLInstanceName -ErrorCategory NotImplemented + } + + # Get the Availabilty Group if it exists + $availabilityGroup = $serverObject.AvailabilityGroups[$AvailabilityGroupName] + + # Make sure we're communicating with the primary replica in order to make changes to the replica + if ( $availabilityGroup ) + { + while ( $availabilityGroup.LocalReplicaRole -ne 'Primary' ) + { + $primaryServerObject = Connect-SQL -SQLServer $availabilityGroup.PrimaryReplicaServerName + $availabilityGroup = $primaryServerObject.AvailabilityGroups[$AvailabilityGroupName] + } + } + + switch ( $Ensure ) + { + Absent + { + if ( $availabilityGroup ) + { + $availabilityGroupReplica = $availabilityGroup.AvailabilityReplicas[$Name] + + if ( $availabilityGroupReplica ) + { + try + { + Remove-SqlAvailabilityReplica -InputObject $availabilityGroupReplica -Confirm:$false -ErrorAction Stop + } + catch + { + throw New-TerminatingError -ErrorType RemoveAvailabilityGroupReplicaFailed -FormatArgs $Name -ErrorCategory ResourceUnavailable -InnerException $_.Exception + } + } + } + } + + Present + { + $clusterServiceName = 'NT SERVICE\ClusSvc' + $ntAuthoritySystemName = 'NT AUTHORITY\SYSTEM' + $availabilityGroupManagementPerms = @('Connect SQL','Alter Any Availability Group','View Server State') + $clusterPermissionsPresent = $false + + foreach ( $loginName in @( $clusterServiceName, $ntAuthoritySystemName ) ) + { + if ( $serverObject.Logins[$loginName] -and -not $clusterPermissionsPresent ) + { + $testLoginEffectivePermissionsParams = @{ + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + LoginName = $loginName + Permissions = $availabilityGroupManagementPerms + } + + $clusterPermissionsPresent = Test-LoginEffectivePermissions @testLoginEffectivePermissionsParams + + if ( -not $clusterPermissionsPresent ) + { + switch ( $loginName ) + { + $clusterServiceName + { + New-VerboseMessage -Message "The recommended account '$loginName' is missing one or more of the following permissions: $( $availabilityGroupManagementPerms -join ', ' ). Trying with '$ntAuthoritySystemName'." + } + + $ntAuthoritySystemName + { + New-VerboseMessage -Message "'$loginName' is missing one or more of the following permissions: $( $availabilityGroupManagementPerms -join ', ' )" + } + } + } + } + elseif ( -not $clusterPermissionsPresent ) + { + switch ( $loginName ) + { + $clusterServiceName + { + New-VerboseMessage -Message "The recommended login '$loginName' is not present. Trying with '$ntAuthoritySystemName'." + } + + $ntAuthoritySystemName + { + New-VerboseMessage -Message "The login '$loginName' is not present." + } + } + } + } + + # If neither 'NT SERVICE\ClusSvc' or 'NT AUTHORITY\SYSTEM' have the required permissions, throw an error. + if ( -not $clusterPermissionsPresent ) + { + throw New-TerminatingError -ErrorType ClusterPermissionsMissing -FormatArgs $SQLServer,$SQLInstanceName -ErrorCategory SecurityError + } + + # Make sure a database mirroring endpoint exists. + $endpoint = $serverObject.Endpoints | Where-Object { $_.EndpointType -eq 'DatabaseMirroring' } + if ( -not $endpoint ) + { + throw New-TerminatingError -ErrorType DatabaseMirroringEndpointNotFound -FormatArgs $SQLServer,$SQLInstanceName -ErrorCategory ObjectNotFound + } + + # If a hostname for the endpoint was not specified, define it now. + if ( -not $EndpointHostName ) + { + $EndpointHostName = $serverObject.NetName + } + + # Get the endpoint port + $endpointPort = $endpoint.Protocol.Tcp.ListenerPort + + # Determine if the Availabilty Group exists on the instance + if ( $availabilityGroup ) + { + # Make sure the replia exists on the instance. If the availability group exists, the replica should exist. + $availabilityGroupReplica = $availabilityGroup.AvailabilityReplicas[$Name] + if ( $availabilityGroupReplica ) + { + if ( $AvailabilityMode -ne $availabilityGroupReplica.AvailabilityMode ) + { + $availabilityGroupReplica.AvailabilityMode = $AvailabilityMode + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + if ( $BackupPriority -ne $availabilityGroupReplica.BackupPriority ) + { + $availabilityGroupReplica.BackupPriority = $BackupPriority + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + # Make sure ConnectionModeInPrimaryRole has a value in order to avoid false positive matches when the parameter is not defined + if ( ( -not [string]::IsNullOrEmpty($ConnectionModeInPrimaryRole) ) -and ( $ConnectionModeInPrimaryRole -ne $availabilityGroupReplica.ConnectionModeInPrimaryRole ) ) + { + $availabilityGroupReplica.ConnectionModeInPrimaryRole = $ConnectionModeInPrimaryRole + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + # Make sure ConnectionModeInSecondaryRole has a value in order to avoid false positive matches when the parameter is not defined + if ( ( -not [string]::IsNullOrEmpty($ConnectionModeInSecondaryRole) ) -and ( $ConnectionModeInSecondaryRole -ne $availabilityGroupReplica.ConnectionModeInSecondaryRole ) ) + { + $availabilityGroupReplica.ConnectionModeInSecondaryRole = $ConnectionModeInSecondaryRole + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + # Break out the EndpointUrl properties + $currentEndpointProtocol, $currentEndpointHostName, $currentEndpointPort = $availabilityGroupReplica.EndpointUrl.Replace('//','').Split(':') + + if ( $endpoint.Protocol.Tcp.ListenerPort -ne $currentEndpointPort ) + { + $newEndpointUrl = $availabilityGroupReplica.EndpointUrl.Replace($currentEndpointPort,$endpoint.Protocol.Tcp.ListenerPort) + $availabilityGroupReplica.EndpointUrl = $newEndpointUrl + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + if ( $EndpointHostName -ne $currentEndpointHostName ) + { + $newEndpointUrl = $availabilityGroupReplica.EndpointUrl.Replace($currentEndpointHostName,$EndpointHostName) + $availabilityGroupReplica.EndpointUrl = $newEndpointUrl + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + if ( $currentEndpointProtocol -ne 'TCP' ) + { + $newEndpointUrl = $availabilityGroupReplica.EndpointUrl.Replace($currentEndpointProtocol,'TCP') + $availabilityGroupReplica.EndpointUrl = $newEndpointUrl + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + if ( $FailoverMode -ne $availabilityGroupReplica.FailoverMode ) + { + $availabilityGroupReplica.FailoverMode = $FailoverMode + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + if ( $ReadOnlyRoutingConnectionUrl -ne $availabilityGroupReplica.ReadOnlyRoutingConnectionUrl ) + { + $availabilityGroupReplica.ReadOnlyRoutingConnectionUrl = $ReadOnlyRoutingConnectionUrl + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + + if ( $ReadOnlyRoutingList -ne $availabilityGroupReplica.ReadOnlyRoutingList ) + { + $availabilityGroupReplica.ReadOnlyRoutingList = $ReadOnlyRoutingList + Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityGroupReplica + } + } + else + { + throw New-TerminatingError -ErrorType ReplicaNotFound -FormatArgs $Name,$SQLInstanceName -ErrorCategory ResourceUnavailable + } + } + else + { + # Connect to the instance that is supposed to house the primary replica + $primaryReplicaServerObject = Connect-SQL -SQLServer $PrimaryReplicaSQLServer -SQLInstanceName $PrimaryReplicaSQLInstanceName + + # Verify the Availability Group exists on the supplied primary replica + $primaryReplicaAvailabilityGroup = $primaryReplicaServerObject.AvailabilityGroups[$AvailabilityGroupName] + if ( $primaryReplicaAvailabilityGroup ) + { + # Make sure the instance defined as the primary replica in the parameters is actually the primary replica + if ( $primaryReplicaAvailabilityGroup.LocalReplicaRole -ne 'Primary' ) + { + New-VerboseMessage -Message "The instance '$PrimaryReplicaSQLServer\$PrimaryReplicaSQLInstanceName' is not currently the primary replica. Connecting to '$($primaryReplicaAvailabilityGroup.PrimaryReplicaServerName)'." + + $primaryReplicaServerObject = Connect-SQL -SQLServer $primaryReplicaAvailabilityGroup.PrimaryReplicaServerName + $primaryReplicaAvailabilityGroup = $primaryReplicaServerObject.AvailabilityGroups[$AvailabilityGroupName] + } + + # Build the endpoint URL + $endpointUrl = "TCP://$($EndpointHostName):$($endpointPort)" + + $newAvailabilityGroupReplicaParams = @{ + Name = $Name + InputObject = $primaryReplicaAvailabilityGroup + AvailabilityMode = $AvailabilityMode + EndpointUrl = $endpointUrl + FailoverMode = $FailoverMode + Verbose = $false + } + + if ( $BackupPriority ) + { + $newAvailabilityGroupReplicaParams.Add('BackupPriority',$BackupPriority) + } + + if ( $ConnectionModeInPrimaryRole ) + { + $newAvailabilityGroupReplicaParams.Add('ConnectionModeInPrimaryRole',$ConnectionModeInPrimaryRole) + } + + if ( $ConnectionModeInSecondaryRole ) + { + $newAvailabilityGroupReplicaParams.Add('ConnectionModeInSecondaryRole',$ConnectionModeInSecondaryRole) + } + + if ( $ReadOnlyRoutingConnectionUrl ) + { + $newAvailabilityGroupReplicaParams.Add('ReadOnlyRoutingConnectionUrl',$ReadOnlyRoutingConnectionUrl) + } + + if ( $ReadOnlyRoutingList ) + { + $newAvailabilityGroupReplicaParams.Add('ReadOnlyRoutingList',$ReadOnlyRoutingList) + } + + # Create the Availability Group Replica + try + { + $availabilityGroupReplica = New-SqlAvailabilityReplica @newAvailabilityGroupReplicaParams + } + catch + { + throw New-TerminatingError -ErrorType CreateAvailabilityGroupReplicaFailed -FormatArgs $Name,$SQLInstanceName -ErrorCategory OperationStopped -InnerException $_.Exception + } + + # Join the Availability Group Replica to the Availability Group + try + { + $joinAvailabilityGroupResults = Join-SqlAvailabilityGroup -Name $AvailabilityGroupName -InputObject $serverObject + } + catch + { + throw New-TerminatingError -ErrorType JoinAvailabilityGroupFailed -FormatArgs $Name -ErrorCategory OperationStopped -InnerException $_.Exception + } + } + # The Availability Group doesn't exist on the primary replica + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupNotFound -FormatArgs $Name,$PrimaryReplicaSQLInstanceName -ErrorCategory ResourceUnavailable + } + } + } + } +} + +<# + .SYNOPSIS + Determines if the availability group replica is in the desired state. + + .PARAMETER Name + The name of the availability group replica. + + .PARAMETER AvailabilityGroupName + The name of the availability group. + + .PARAMETER SQLServer + Hostname of the SQL Server to be configured. + + .PARAMETER SQLInstanceName + Name of the SQL instance to be configued. + + .PARAMETER PrimaryReplicaSQLServer + Hostname of the SQL Server where the primary replica is expected to be active. If the primary replica is not found here, the resource will attempt to find the host that holds the primary replica and connect to it. + + .PARAMETER PrimaryReplicaSQLInstanceName + Name of the SQL instance where the primary replica lives. + + .PARAMETER Ensure + Specifies if the availability group should be present or absent. Default is Present. + + .PARAMETER AvailabilityMode + Specifies the replica availability mode. Default is 'AsynchronousCommit'. + + .PARAMETER BackupPriority + Specifies the desired priority of the replicas in performing backups. The acceptable values for this parameter are integers from 0 through 100. Of the set of replicas which are online and available, the replica that has the highest priority performs the backup. Default is 50. + + .PARAMETER ConnectionModeInPrimaryRole + Specifies how the availability replica handles connections when in the primary role. + + .PARAMETER ConnectionModeInSecondaryRole + Specifies how the availability replica handles connections when in the secondary role. + + .PARAMETER EndpointHostName + Specifies the hostname or IP address of the availability group replica endpoint. Default is the instance network name which is set in the code because the value can only be determined when connected to the SQL Instance. + + .PARAMETER FailoverMode + Specifies the failover mode. Default is Manual. + + .PARAMETER ReadOnlyRoutingConnectionUrl + Specifies the fully-qualified domain name (FQDN) and port to use when routing to the replica for read only connections. + + .PARAMETER ReadOnlyRoutingList + Specifies an ordered list of replica server names that represent the probe sequence for connection director to use when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + Param + ( + [Parameter(Mandatory = $true)] + [String] + $Name, + + [Parameter(Mandatory = $true)] + [String] + $AvailabilityGroupName, + + [Parameter(Mandatory = $true)] + [String] + $SQLServer, + + [Parameter(Mandatory = $true)] + [String] + $SQLInstanceName, + + [Parameter()] + [String] + $PrimaryReplicaSQLServer, + + [Parameter()] + [String] + $PrimaryReplicaSQLInstanceName, + + [Parameter()] + [ValidateSet('Present','Absent')] + [String] + $Ensure = 'Present', + + [Parameter()] + [ValidateSet('AsynchronousCommit','SynchronousCommit')] + [String] + $AvailabilityMode = 'AsynchronousCommit', + + [Parameter()] + [ValidateRange(0,100)] + [UInt32] + $BackupPriority = 50, + + [Parameter()] + [ValidateSet('AllowAllConnections','AllowReadWriteConnections')] + [String] + $ConnectionModeInPrimaryRole, + + [Parameter()] + [ValidateSet('AllowNoConnections','AllowReadIntentConnectionsOnly','AllowAllConnections')] + [String] + $ConnectionModeInSecondaryRole, + + [Parameter()] + [String] + $EndpointHostName, + + [Parameter()] + [ValidateSet('Automatic','Manual')] + [String] + $FailoverMode = 'Manual', + + [Parameter()] + [String] + $ReadOnlyRoutingConnectionUrl, + + [Parameter()] + [String[]] + $ReadOnlyRoutingList + ) + + $getTargetResourceParameters = @{ + SQLInstanceName = $SQLInstanceName + SQLServer = $SQLServer + Name = $Name + AvailabilityGroupName = $AvailabilityGroupName + } + + # Assume this will pass. We will determine otherwise later + $result = $true + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + switch ($Ensure) + { + 'Absent' + { + if ( $getTargetResourceResult.Ensure -eq 'Absent' ) + { + $result = $true + } + else + { + $result = $false + } + } + + 'Present' + { + $parametersToCheck = @( + 'Name', + 'AvailabilityGroupName', + 'SQLServer', + 'SQLInstanceName', + 'Ensure', + 'AvailabilityMode', + 'BackupPriority', + 'ConnectionModeInPrimaryRole', + 'ConnectionModeInSecondaryRole', + 'FailoverMode', + 'ReadOnlyRoutingConnectionUrl', + 'ReadOnlyRoutingList' + ) + + if ( $getTargetResourceResult.Ensure -eq 'Present' ) + { + # PsBoundParameters won't work here because it doesn't account for default values + foreach ( $parameter in $MyInvocation.MyCommand.Parameters.GetEnumerator() ) + { + $parameterName = $parameter.Key + $parameterValue = Get-Variable -Name $parameterName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Value + + # Make sure we don't try to validate a common parameter + if ( $parametersToCheck -contains $parameterName ) + { + # If the parameter is Null, a value wasn't provided + if ( -not [string]::IsNullOrEmpty($parameterValue) ) + { + if ( $getTargetResourceResult.($parameterName) -ne $parameterValue ) + { + New-VerboseMessage -Message "'$($parameterName)' should be '$($parameterValue)' but is '$($getTargetResourceResult.($parameterName))'" + + $result = $false + } + } + } + } + + # Get the Endpoint URL properties + $currentEndpointProtocol, $currentEndpointHostName, $currentEndpointPort = $getTargetResourceResult.EndpointUrl.Replace('//','').Split(':') + + if ( -not $EndpointHostName ) + { + $EndpointHostName = $getTargetResourceResult.SQLServerNetName + } + + # Verify the hostname in the endpoint URL is correct + if ( $EndpointHostName -ne $currentEndpointHostName ) + { + New-VerboseMessage -Message "'EndpointHostName' should be '$EndpointHostName' but is '$currentEndpointHostName'" + $result = $false + } + + # Verify the protocol in the endpoint URL is correct + if ( 'TCP' -ne $currentEndpointProtocol ) + { + New-VerboseMessage -Message "'EndpointProtocol' should be 'TCP' but is '$currentEndpointProtocol'" + $result = $false + } + + # Verify the port in the endpoint URL is correct + if ( $getTargetResourceResult.EndpointPort -ne $currentEndpointPort ) + { + New-VerboseMessage -Message "'EndpointPort' should be '$($getTargetResourceResult.EndpointPort)' but is '$currentEndpointPort'" + $result = $false + } + } + else + { + $result = $false + } + } + } + + return $result +} + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.schema.mof b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.schema.mof new file mode 100644 index 000000000..56b8c1af3 --- /dev/null +++ b/DSCResources/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.schema.mof @@ -0,0 +1,20 @@ +[ClassVersion("1.0.0.0"), FriendlyName("xSQLServerAlwaysOnAvailabilityGroupReplica")] +class MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica : OMI_BaseResource +{ + [Key, Description("The name of the availability group replica. For named instances this must be in the following format SQLServer\\InstanceName.")] String Name; + [Key, Description("The name of the availability group.")] String AvailabilityGroupName; + [Required, Description("Hostname of the SQL Server to be configured.")] String SQLServer; + [Key, Description("Name of the SQL instance to be configured.")] String SQLInstanceName; + [Write, Description("Hostname of the SQL Server where the primary replica is expected to be active. If the primary replica is not found here, the resource will attempt to find the host that holds the primary replica and connect to it.")] String PrimaryReplicaSQLServer; + [Write, Description("Name of the SQL instance where the primary replica lives.")] String PrimaryReplicaSQLInstanceName; + [Write, Description("Specifies if the availability group replica should be present or absent. Default is Present."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("Specifies the replica availability mode. Default is 'AsynchronousCommit'."), ValueMap{"AsynchronousCommit","SynchronousCommit"}, Values{"AsynchronousCommit","SynchronousCommit"}] String AvailabilityMode; + [Write, Description("Specifies the desired priority of the replicas in performing backups. The acceptable values for this parameter are: integers from 0 through 100. Of the set of replicas which are online and available, the replica that has the highest priority performs the backup. Default is 50.")] UInt32 BackupPriority; + [Write, Description("Specifies how the availability replica handles connections when in the primary role."), ValueMap{"AllowAllConnections","AllowReadWriteConnections"}, Values{"AllowAllConnections","AllowReadWriteConnections"}] String ConnectionModeInPrimaryRole; + [Write, Description("Specifies how the availability replica handles connections when in the secondary role."), ValueMap{"AllowNoConnections","AllowReadIntentConnectionsOnly","AllowAllConnections"}, Values{"AllowNoConnections","AllowReadIntentConnectionsOnly","AllowAllConnections"}] String ConnectionModeInSecondaryRole; + [Write, Description("Specifies the hostname or IP address of the availability group replica endpoint. Default is the instance network name which is set in the code because the value can only be determined when connected to the SQL Instance.")] String EndpointHostName; + [Write, Description("Specifies the failover mode. Default is 'Manual'."), ValueMap{"Automatic","Manual"}, Values{"Automatic","Manual"}] String FailoverMode; + [Write, Description("Specifies the fully-qualified domain name (FQDN) and port to use when routing to the replica for read only connections.")] String ReadOnlyRoutingConnectionUrl; + [Write, Description("Specifies an ordered list of replica server names that represent the probe sequence for connection director to use when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group.")] String ReadOnlyRoutingList[]; + [Read, Description("Output the NetName property from the SQL Server object. Used by Get-TargetResource")] String SqlServerNetName; +}; diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.psm1 b/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.psm1 index 85f074265..41274e9d0 100644 --- a/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.psm1 +++ b/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.psm1 @@ -9,12 +9,12 @@ Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Pare .PARAMETER Ensure *** Not used in this function *** HADR is Present (enabled) or Absent (disabled). - + .PARAMETER SQLServer Hostname of the SQL Server to be configured. - + .PARAMETER SQLInstanceName - Name of the SQL instance to be configued. + Name of the SQL instance to be configued. #> function Get-TargetResource { @@ -36,11 +36,44 @@ function Get-TargetResource $SQLInstanceName ) - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + $isAlwaysOnEnabled = $sqlServerObject.IsHadrEnabled + if ($isAlwaysOnEnabled -eq $true) + { + $statusString = 'enabled' + } + elseif ($isAlwaysOnEnabled -eq $false) + { + $statusString = 'disabled' + } + else + { + # This is a validation test for issue #519. + try { + if ($isAlwaysOnEnabled -eq $null) + { + throw 'Server.IsHadrEnabled was set to $null.' + } + else + { + $statusString = $isAlwaysOnEnabled + + throw 'Server.IsHadrEnabled was set to unexpected value.' + } + } + catch + { + throw New-TerminatingError -ErrorType UnexpectedAlwaysOnStatus -FormatArgs $statusString -ErrorCategory InvalidResult -InnerException $_.Exception + } + } - New-VerboseMessage -Message ( 'SQL Always On is {0} on "{1}\{2}".' -f @{$false='disabled'; $true='enabled'}[$sql.IsHadrEnabled],$SQLServer,$SQLInstanceName ) - return @{ IsHadrEnabled = $sql.IsHadrEnabled } + New-VerboseMessage -Message ( 'SQL Always On is {0} on "{1}\{2}".' -f $statusString, $SQLServer, $SQLInstanceName ) + + return @{ + IsHadrEnabled = $isAlwaysOnEnabled + } } <# @@ -52,7 +85,7 @@ function Get-TargetResource .PARAMETER SQLServer Hostname of the SQL Server to be configured. - + .PARAMETER SQLInstanceName Name of the SQL instance to be configued. @@ -128,10 +161,10 @@ function Set-TargetResource .PARAMETER Ensure HADR is Present (enabled) or Absent (disabled). - + .PARAMETER SQLServer Hostname of the SQL Server to be configured. - + .PARAMETER SQLInstanceName Name of the SQL instance to be configued. @@ -162,7 +195,7 @@ function Test-TargetResource [Int32] $RestartTimeout = 120 ) - + # Determine the current state of Always On $params = @{ Ensure = $Ensure @@ -171,8 +204,8 @@ function Test-TargetResource } $state = Get-TargetResource @params - - # Determine what the desired state of Always On is + + # Determine what the desired state of Always On is $hadrDesiredState = @{ 'Present' = $true; 'Absent' = $false }[$Ensure] # Determine whether the value matches the desired state diff --git a/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.schema.mof b/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.schema.mof index 083ef2a04..893edbe11 100644 --- a/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.schema.mof +++ b/DSCResources/MSFT_xSQLServerAlwaysOnService/MSFT_xSQLServerAlwaysOnService.schema.mof @@ -1,7 +1,7 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerAlwaysOnService")] class MSFT_xSQLServerAlwaysOnService : OMI_BaseResource { - [Required, Description("HADR is Present (enabled) or Absent (disabled)"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Required, Description("An enumerated value that describes if SQL server should have AlwaysOn property present or absent."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [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, Description("The length of time, in seconds, to wait for the service to restart. Default is 120 seconds.")] Sint32 RestartTimeout; diff --git a/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.psm1 b/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.psm1 index 34376718a..87933a802 100644 --- a/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.psm1 +++ b/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.psm1 @@ -1,8 +1,22 @@ -$ErrorActionPreference = "Stop" +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force +<# + .SYNOPSIS + Returns the current state of the Availabilty Group listener. -$script:currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -ErrorAction Stop + .PARAMETER InstanceName + The SQL Server instance name of the primary replica. Default value is 'MSSQLSERVER'. + .PARAMETER NodeName + The host name or FQDN of the primary replica. + + .PARAMETER Name + The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). + + .PARAMETER AvailabilityGroup + The name of the availability group to which the availability group listener is or will be connected. +#> function Get-TargetResource { [CmdletBinding()] @@ -11,7 +25,7 @@ function Get-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, [Parameter(Mandatory = $true)] [System.String] @@ -27,37 +41,42 @@ function Get-TargetResource $AvailabilityGroup ) - try { - $listener = Get-SQLAlwaysOnAvailabilityGroupListener -Name $Name -AvailabilityGroup $AvailabilityGroup -NodeName $NodeName -InstanceName $InstanceName - - if( $null -ne $listener ) { - New-VerboseMessage -Message "Listener $Name already exist" + try + { + $availabilityGroupListener = Get-SQLAlwaysOnAvailabilityGroupListener -Name $Name -AvailabilityGroup $AvailabilityGroup -NodeName $NodeName -InstanceName $InstanceName - $ensure = "Present" - - $port = [uint16]( $listener | Select-Object -ExpandProperty PortNumber ) + if ($null -ne $availabilityGroupListener) + { + New-VerboseMessage -Message "Listener $Name exist." - $presentIpAddress = $listener.AvailabilityGroupListenerIPAddresses + $ensure = 'Present' + $port = [uint16]( $availabilityGroupListener | Select-Object -ExpandProperty PortNumber ) + $presentIpAddress = $availabilityGroupListener.AvailabilityGroupListenerIPAddresses $dhcp = [bool]( $presentIpAddress | Select-Object -First 1 -ExpandProperty IsDHCP ) $ipAddress = @() - foreach( $currentIpAddress in $presentIpAddress ) { + foreach ($currentIpAddress in $presentIpAddress) + { $ipAddress += "$($currentIpAddress.IPAddress)/$($currentIpAddress.SubnetMask)" - } - } else { + } + } + else + { New-VerboseMessage -Message "Listener $Name does not exist" - $ensure = "Absent" + $ensure = 'Absent' $port = 0 $dhcp = $false $ipAddress = $null } - } catch { + } + catch + { throw New-TerminatingError -ErrorType AvailabilityGroupListenerNotFound -FormatArgs @($Name) -ErrorCategory ObjectNotFound -InnerException $_.Exception } - $returnValue = @{ + return @{ InstanceName = [System.String] $InstanceName NodeName = [System.String] $NodeName Name = [System.String] $Name @@ -67,18 +86,44 @@ function Get-TargetResource Port = [System.UInt16] $port DHCP = [System.Boolean] $dhcp } - - return $returnValue } +<# + .SYNOPSIS + Creates the Availability Group listener. + + .PARAMETER InstanceName + The SQL Server instance name of the primary replica. Default value is 'MSSQLSERVER'. + + .PARAMETER NodeName + The host name or FQDN of the primary replica. + + .PARAMETER Name + The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). + + .PARAMETER Ensure + If the availability group listener should be present or absent. + + .PARAMETER AvailabilityGroup + The name of the availability group to which the availability group listener is or will be connected. + + .PARAMETER 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. + + .PARAMETER Port + The port used for the availability group listener. + + .PARAMETER DHCP + If DHCP should be used for the availability group listener instead of static IP address. +#> function Set-TargetResource { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, [Parameter(Mandatory = $true)] [System.String] @@ -89,9 +134,9 @@ function Set-TargetResource [System.String] $Name, - [ValidateSet("Present","Absent")] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', [Parameter(Mandatory = $true)] [System.String] @@ -106,123 +151,221 @@ function Set-TargetResource [System.Boolean] $DHCP ) - + $parameters = @{ InstanceName = [System.String] $InstanceName NodeName = [System.String] $NodeName Name = [System.String] $Name AvailabilityGroup = [System.String] $AvailabilityGroup } - - $listenerState = Get-TargetResource @parameters - if( $null -ne $listenerState ) { - if( $Ensure -ne "" -and $listenerState.Ensure -ne $Ensure ) { - $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName - - if( $Ensure -eq "Present") { - if( ( $PSCmdlet.ShouldProcess( $Name, "Create listener on $AvailabilityGroup" ) ) ) { + + $availabilityGroupListenerState = Get-TargetResource @parameters + if ($null -ne $availabilityGroupListenerState) + { + if ($Ensure -ne '' -and $availabilityGroupListenerState.Ensure -ne $Ensure) + { + if ($Ensure -eq 'Present') + { + New-VerboseMessage -Message "Create listener on $AvailabilityGroup" + + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $availabilityGroupObject = $sqlServerObject.AvailabilityGroups[$AvailabilityGroup] + if ($availabilityGroupObject) + { $newListenerParams = @{ Name = $Name - Path = "SQLSERVER:\SQL\$NodeName\$InstanceName\AvailabilityGroups\$AvailabilityGroup" + InputObject = $availabilityGroupObject } - if( $Port ) { + if ($Port) + { New-VerboseMessage -Message "Listener port set to $Port" $newListenerParams += @{ Port = $Port } } - if( $DHCP -and $IpAddress.Count -gt 0 ) { + if ($DHCP -and $IpAddress.Count -gt 0) + { New-VerboseMessage -Message "Listener set to DHCP with subnet $IpAddress" $newListenerParams += @{ DhcpSubnet = [string]$IpAddress } - } elseif ( -not $DHCP -and $IpAddress.Count -gt 0 ) { + } + elseif (-not $DHCP -and $IpAddress.Count -gt 0) + { New-VerboseMessage -Message "Listener set to static IP-address(es); $($IpAddress -join ', ')" $newListenerParams += @{ StaticIp = $IpAddress } - } else { - New-VerboseMessage -Message "Listener using DHCP with server default subnet" } - - New-SqlAvailabilityGroupListener @newListenerParams -Verbose:$False | Out-Null # Suppressing Verbose because it prints the entire T-SQL statement otherwise + else + { + New-VerboseMessage -Message 'Listener using DHCP with server default subnet' + } + + New-SqlAvailabilityGroupListener @newListenerParams -ErrorAction Stop | Out-Null } - } else { - if( ( $PSCmdlet.ShouldProcess( $Name, "Remove listener from $AvailabilityGroup" ) ) ) { - Remove-Item "SQLSERVER:\SQL\$NodeName\$InstanceName\AvailabilityGroups\$AvailabilityGroup\AvailabilityGroupListeners\$Name" + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupNotFound -FormatArgs @($AvailabilityGroup,$InstanceName) -ErrorCategory ObjectNotFound } } - } else { - if( $Ensure -ne "" ) { New-VerboseMessage -Message "State is already $Ensure" } - - if( $listenerState.Ensure -eq "Present") { - if( -not $DHCP -and $listenerState.IpAddress.Count -lt $IpAddress.Count ) { # Only able to add a new IP-address, not change existing ones. - New-VerboseMessage -Message "Found at least one new IP-address." - $ipAddressEqual = $False - } else { + else + { + New-VerboseMessage -Message "Remove listener from $AvailabilityGroup" + + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $availabilityGroupObject = $sqlServerObject.AvailabilityGroups[$AvailabilityGroup] + if ($availabilityGroupObject) + { + $availabilityGroupListenerObject = $availabilityGroupObject.AvailabilityGroupListeners[$Name] + if ($availabilityGroupListenerObject) + { + $availabilityGroupListenerObject.Drop() + } + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupListenerNotFound -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupNotFound -FormatArgs @($AvailabilityGroup,$InstanceName) -ErrorCategory ObjectNotFound + } + } + } + else + { + if ($Ensure -ne '') + { + New-VerboseMessage -Message "State is already $Ensure" + } + + if ($availabilityGroupListenerState.Ensure -eq 'Present') + { + if (-not $DHCP -and $availabilityGroupListenerState.IpAddress.Count -lt $IpAddress.Count) # Only able to add a new IP-address, not change existing ones. + { + New-VerboseMessage -Message 'Found at least one new IP-address.' + $ipAddressEqual = $false + } + else + { # No new IP-address - if( $null -eq $IpAddress -or -not ( Compare-Object -ReferenceObject $IpAddress -DifferenceObject $listenerState.IpAddress ) ) { - $ipAddressEqual = $True - } else { - throw New-TerminatingError -ErrorType AvailabilityGroupListenerIPChangeError -FormatArgs @($($IpAddress -join ', '),$($listenerState.IpAddress -join ', ')) -ErrorCategory InvalidOperation + if ($null -eq $IpAddress -or -not ( Compare-Object -ReferenceObject $IpAddress -DifferenceObject $availabilityGroupListenerState.IpAddress)) + { + $ipAddressEqual = $true + } + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupListenerIPChangeError -FormatArgs @($($IpAddress -join ', '),$($availabilityGroupListenerState.IpAddress -join ', ')) -ErrorCategory InvalidOperation } } - if( $($PSBoundParameters.ContainsKey('DHCP')) -and $listenerState.DHCP -ne $DHCP ) { - throw New-TerminatingError -ErrorType AvailabilityGroupListenerDHCPChangeError -FormatArgs @( $DHCP, $($listenerState.DHCP) ) -ErrorCategory InvalidOperation + if ($($PSBoundParameters.ContainsKey('DHCP')) -and $availabilityGroupListenerState.DHCP -ne $DHCP) + { + throw New-TerminatingError -ErrorType AvailabilityGroupListenerDHCPChangeError -FormatArgs @( $DHCP, $($availabilityGroupListenerState.DHCP) ) -ErrorCategory InvalidOperation } - - if( $listenerState.Port -ne $Port -or -not $ipAddressEqual ) { - New-VerboseMessage -Message "Listener differ in configuration." - - if( $listenerState.Port -ne $Port ) { - if( ( $PSCmdlet.ShouldProcess( $Name, "Changing port configuration" ) ) ) { - $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName - - $setListenerParams = @{ - Path = "SQLSERVER:\SQL\$NodeName\$InstanceName\AvailabilityGroups\$AvailabilityGroup\AvailabilityGroupListeners\$Name" - Port = $Port + + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $availabilityGroupObject = $sqlServerObject.AvailabilityGroups[$AvailabilityGroup] + if ($availabilityGroupObject) + { + $availabilityGroupListenerObject = $availabilityGroupObject.AvailabilityGroupListeners[$Name] + if ($availabilityGroupListenerObject) + { + if ($availabilityGroupListenerState.Port -ne $Port -or -not $ipAddressEqual) + { + New-VerboseMessage -Message 'Listener differ in configuration.' + + if ($availabilityGroupListenerState.Port -ne $Port) + { + New-VerboseMessage -Message 'Changing port configuration' + + $setListenerParams = @{ + InputObject = $availabilityGroupListenerObject + Port = $Port + } + + Set-SqlAvailabilityGroupListener @setListenerParams -ErrorAction Stop | Out-Null } - Set-SqlAvailabilityGroupListener @setListenerParams -Verbose:$False | Out-Null # Suppressing Verbose because it prints the entire T-SQL statement otherwise - } - } + if (-not $ipAddressEqual) + { + New-VerboseMessage -Message 'Adding IP-address(es)' - if( -not $ipAddressEqual ) { - if( ( $PSCmdlet.ShouldProcess( $Name, "Adding IP-address(es)" ) ) ) { - $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName - - $newIpAddress = @() - - foreach( $currentIpAddress in $IpAddress ) { - if( -not ( $listenerState.IpAddress -contains $currentIpAddress ) ) { - $newIpAddress += $currentIpAddress + $newIpAddress = @() + + foreach ($currentIpAddress in $IpAddress) + { + if (-not ( $availabilityGroupListenerState.IpAddress -contains $currentIpAddress)) + { + $newIpAddress += $currentIpAddress + } + } + + $setListenerParams = @{ + InputObject = $availabilityGroupListenerObject + StaticIp = $newIpAddress } - } - - $setListenerParams = @{ - Path = "SQLSERVER:\SQL\$NodeName\$InstanceName\AvailabilityGroups\$AvailabilityGroup\AvailabilityGroupListeners\$Name" - StaticIp = $newIpAddress - } - Add-SqlAvailabilityGroupListenerStaticIp @setListenerParams -Verbose:$False | Out-Null # Suppressing Verbose because it prints the entire T-SQL statement otherwise + Add-SqlAvailabilityGroupListenerStaticIp @setListenerParams -ErrorAction Stop | Out-Null + } + } + else + { + New-VerboseMessage -Message 'Listener configuration is already correct.' } } - - } else { - New-VerboseMessage -Message "Listener configuration is already correct." + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupListenerNotFound -ErrorCategory ObjectNotFound + } + } + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupNotFound -FormatArgs @($AvailabilityGroup,$InstanceName) -ErrorCategory ObjectNotFound } - } else { - throw New-TerminatingError -ErrorType AvailabilityGroupListenerNotFound -ErrorCategory ObjectNotFound } } - } else { + } + else + { throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult } } +<# + .SYNOPSIS + Tests if the the Availability Group listener is in desired state. + + .PARAMETER InstanceName + The SQL Server instance name of the primary replica. Default value is 'MSSQLSERVER'. + + .PARAMETER NodeName + The host name or FQDN of the primary replica. + + .PARAMETER Name + The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO). + + .PARAMETER Ensure + If the availability group listener should be present or absent. + + .PARAMETER AvailabilityGroup + The name of the availability group to which the availability group listener is or will be connected. + + .PARAMETER 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. + + .PARAMETER Port + The port used for the availability group listener. + + .PARAMETER DHCP + If DHCP should be used for the availability group listener instead of static IP address. +#> function Test-TargetResource { [CmdletBinding()] @@ -231,7 +374,7 @@ function Test-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, [Parameter(Mandatory = $true)] [System.String] @@ -242,9 +385,9 @@ function Test-TargetResource [System.String] $Name, - [ValidateSet("Present","Absent")] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', [Parameter(Mandatory = $true)] [System.String] @@ -266,34 +409,42 @@ function Test-TargetResource Name = [System.String] $Name AvailabilityGroup = [System.String] $AvailabilityGroup } - + New-VerboseMessage -Message "Testing state of listener $Name" - - $listenerState = Get-TargetResource @parameters - if( $null -ne $listenerState ) { - if( $null -eq $IpAddress -or ($null -ne $listenerState.IpAddress -and -not ( Compare-Object -ReferenceObject $IpAddress -DifferenceObject $listenerState.IpAddress ) ) ) { + + $availabilityGroupListenerState = Get-TargetResource @parameters + if ($null -ne $availabilityGroupListenerState) + { + if ($null -eq $IpAddress -or ($null -ne $availabilityGroupListenerState.IpAddress -and -not ( Compare-Object -ReferenceObject $IpAddress -DifferenceObject $availabilityGroupListenerState.IpAddress))) + { $ipAddressEqual = $true - } else { + } + else + { $ipAddressEqual = $false } - + [System.Boolean] $result = $false - if( $listenerState.Ensure -eq $Ensure) { - if( $Ensure -eq 'Absent' ) { + if ($availabilityGroupListenerState.Ensure -eq $Ensure) + { + if ($Ensure -eq 'Absent') + { $result = $true } } - if( -not $($PSBoundParameters.ContainsKey('Ensure')) -or $Ensure -eq "Present" ) { - if( ( $Port -eq "" -or $listenerState.Port -eq $Port) -and - $ipAddressEqual -and - ( -not $($PSBoundParameters.ContainsKey('DHCP')) -or $listenerState.DHCP -eq $DHCP ) ) + if (-not $($PSBoundParameters.ContainsKey('Ensure')) -or $Ensure -eq 'Present') + { + if (($Port -eq "" -or $availabilityGroupListenerState.Port -eq $Port) -and + $ipAddressEqual -and + (-not $($PSBoundParameters.ContainsKey('DHCP')) -or $availabilityGroupListenerState.DHCP -eq $DHCP)) { $result = $true } } - - } else { + } + else + { throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult } @@ -320,23 +471,24 @@ function Get-SQLAlwaysOnAvailabilityGroupListener [Parameter(Mandatory = $true)] [System.String] - $NodeName + $NodeName ) - $instance = Get-SQLPSInstance -InstanceName $InstanceName -NodeName $NodeName - $Path = "$($instance.PSPath)\AvailabilityGroups\$AvailabilityGroup\AvailabilityGroupListeners" + Write-Debug "Connecting to availability group $Name as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" + + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName - Write-Debug "Connecting to $Path as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" - - [String[]] $presentListener = Get-ChildItem $Path - if( $presentListener.Count -ne 0 -and $presentListener.Contains("[$Name]") ) { - Write-Debug "Connecting to availability group $Name as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" - $listener = Get-Item "$Path\$Name" - } else { - $listener = $null - } + $availabilityGroupObject = $sqlServerObject.AvailabilityGroups[$AvailabilityGroup] + if ($availabilityGroupObject) + { + $availabilityGroupListener = $availabilityGroupObject.AvailabilityGroupListeners[$Name] + } + else + { + throw New-TerminatingError -ErrorType AvailabilityGroupNotFound -FormatArgs @($AvailabilityGroup,$InstanceName) -ErrorCategory ObjectNotFound + } - return $listener + return $availabilityGroupListener } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.schema.mof b/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.schema.mof index 3742eb51a..29c9c83df 100644 --- a/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.schema.mof +++ b/DSCResources/MSFT_xSQLServerAvailabilityGroupListener/MSFT_xSQLServerAvailabilityGroupListener.schema.mof @@ -5,9 +5,9 @@ class MSFT_xSQLServerAvailabilityGroupListener : OMI_BaseResource [Key, Description("The SQL Server instance name of the primary replica.")] String InstanceName; [Required, Description("The host name or FQDN of the primary replica.")] String NodeName; [Required, Description("The name of the availability group listener, max 15 characters. This name will be used as the Virtual Computer Object (VCO).")] String Name; - [Write, Description("If the availability group listener should be present or absent."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("If the availability group listener should be present or absent. Default value is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Key, Description("The name of the availability group to which the availability group listener is or will be connected.")] String AvailabilityGroup; - [Write, Description("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.")] String IpAddress[]; + [Write, Description("The IP address used for the availability group listener, in the format 192.168.10.45/255.255.252.0. If using DHCP, 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.")] String IpAddress[]; [Write, Description("The port used for the availability group listener")] UInt16 Port; [Write, Description("If DHCP should be used for the availability group listener instead of static IP address.")] Boolean DHCP; }; diff --git a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 index f0614f322..a22eb7dfc 100644 --- a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 +++ b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.psm1 @@ -1,13 +1,10 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Force - +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 @@ -32,32 +29,33 @@ function Get-TargetResource [OutputType([System.Collections.Hashtable])] param ( - [ValidateSet('Present','Absent')] - [System.String] - $Ensure, - - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Database, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name, - [parameter(Mandatory = $true)] - [ValidateSet('Grant','Deny')] + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny','GrantWithGrant')] [System.String] $PermissionState, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String[]] $Permissions, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer = $env:COMPUTERNAME, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName = 'MSSQLSERVER' ) @@ -66,39 +64,73 @@ function Get-TargetResource 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) + Write-Verbose -Message "Getting permissions for user $Name in database $Database" + $currentEnsure = 'Absent' + + if ($sqlDatabaseObject = $sqlServerObject.Databases[$Database]) { - $resultOfPermissionCompare = Compare-Object -ReferenceObject $Permissions ` - -DifferenceObject $getSqlDatabasePermissionResult - if ($null -eq $resultOfPermissionCompare) + if ($sqlServerObject.Logins[$Name]) { - $Ensure = 'Present' + # Initialize variable permission + [System.String[]] $getSqlDatabasePermissionResult = @() + + try + { + $databasePermissionInfo = $sqlDatabaseObject.EnumDatabasePermissions($Name) | 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") + { + $getSqlDatabasePermissionResult += $currentPermissionProperty + } + } + } + } + catch + { + throw New-TerminatingError -ErrorType FailedToEnumDatabasePermissions ` + -FormatArgs @($Name, $Database, $SQLServer, $SQLInstanceName) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } + } else { - $Ensure = 'Absent' + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$SQLServer,$SQLInstanceName) ` + -ErrorCategory ObjectNotFound ` + -InnerException $_.Exception } } - else + else { - $Ensure = 'Absent' + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidResult ` + -InnerException $_.Exception + } + + if ($getSqlDatabasePermissionResult) + { + $resultOfPermissionCompare = Compare-Object -ReferenceObject $Permissions ` + -DifferenceObject $getSqlDatabasePermissionResult + if ($null -eq $resultOfPermissionCompare) + { + $currentEnsure = 'Present' + } } } - else - { - throw New-TerminatingError -ErrorType ConnectSQLError ` - -FormatArgs @($SQLServer,$SQLInstanceName) ` - -ErrorCategory InvalidOperation - } - + $returnValue = @{ - Ensure = $Ensure + Ensure = $currentEnsure Database = $Database Name = $Name PermissionState = $PermissionState @@ -140,69 +172,148 @@ function Set-TargetResource [CmdletBinding()] param ( + [Parameter()] [ValidateSet('Present','Absent')] [System.String] - $Ensure = 'Present', + $Ensure, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Database, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name, - [parameter(Mandatory = $true)] - [ValidateSet('Grant','Deny')] + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny','GrantWithGrant')] [System.String] $PermissionState, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String[]] $Permissions, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer = $env:COMPUTERNAME, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName = 'MSSQLSERVER' ) $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - + if ($sqlServerObject) { - Write-Verbose -Message "Setting permissions of database '$Database' for login '$Name'" + Write-Verbose -Message "Setting permissions of database $Database for login $Name" - if ($Ensure -eq 'Present') + if ($sqlDatabaseObject = $sqlServerObject.Databases[$Database]) { - Add-SqlDatabasePermission -SqlServerObject $sqlServerObject ` - -Name $Name ` - -Database $Database ` - -PermissionState $PermissionState ` - -Permissions $Permissions - - New-VerboseMessage -Message "$PermissionState - SQL Permissions for $Name, successfullly added in $Database" + if ($sqlServerObject.Logins[$Name]) + { + if ( -not ($sqlDatabaseObject.Users[$Name])) + { + try + { + New-VerboseMessage -Message "Adding SQL login $Name as a user of database $Database" + $sqlDatabaseUser = New-Object -TypeName Microsoft.SqlServer.Management.Smo.User -ArgumentList ($sqlDatabaseObject,$Name) + $sqlDatabaseUser.Login = $Name + $sqlDatabaseUser.Create() + } + catch + { + throw New-TerminatingError -ErrorType AddLoginDatabaseSetError ` + -FormatArgs @($SQLServer, $SQLInstanceName, $Name, $Database) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } + } + + if ($sqlDatabaseObject.Users[$Name]) + { + try + { + $permissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet + + foreach ($permission in $permissions) + { + $permissionSet."$permission" = $true + } + + switch ($Ensure) + { + 'Present' + { + New-VerboseMessage -Message ('{0} the permissions ''{1}'' to the database {2} on the server {3}\{4}' ` + -f $PermissionState, ($Permissions -join ','), $Database, $SQLServer, $SQLInstanceName) + + switch ($PermissionState) + { + 'GrantWithGrant' + { + $sqlDatabaseObject.Grant($permissionSet, $Name, $true) + } + + 'Grant' + { + $sqlDatabaseObject.Grant($permissionSet, $Name) + } + + 'Deny' + { + $sqlDatabaseObject.Deny($permissionSet, $Name) + } + } + } + + 'Absent' + { + New-VerboseMessage -Message ('Revoking {0} permissions {1} to the database {2} on the server {3}\{4}' ` + -f $PermissionState, ($Permissions -join ','), $Database, $SQLServer, $SQLInstanceName) + + if ($PermissionState -eq 'GrantWithGrant') + { + $sqlDatabaseObject.Revoke($permissionSet, $Name, $false, $true) + } + else + { + $sqlDatabaseObject.Revoke($permissionSet, $Name) + } + } + } + } + catch + { + throw New-TerminatingError -ErrorType FailedToSetPermissionDatabase ` + -FormatArgs @($Name, $Database, $SQLServer, $SQLInstanceName) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } + } + } + else + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name,$SQLServer,$SQLInstanceName) ` + -ErrorCategory ObjectNotFound ` + -InnerException $_.Exception + } } 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" + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database,$SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidResult ` + -InnerException $_.Exception } } - else - { - throw New-TerminatingError -ErrorType ConnectSQLError ` - -FormatArgs @($SQLServer,$SQLInstanceName) ` - -ErrorCategory InvalidOperation - } } <# @@ -236,43 +347,64 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( + [Parameter()] [ValidateSet('Present','Absent')] [System.String] - $Ensure = 'Present', + $Ensure, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Database, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name, - [parameter(Mandatory = $true)] - [ValidateSet('Grant','Deny')] + [Parameter(Mandatory = $true)] + [ValidateSet('Grant','Deny','GrantWithGrant')] [System.String] $PermissionState, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String[]] $Permissions, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer = $env:COMPUTERNAME, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName = 'MSSQLSERVER' ) - Write-Verbose -Message "Evaluating permissions for user '$Name' in database '$Database'." + Write-Verbose -Message "Testing permissions for user $Name in database $Database." + $getTargetResourceParameters = @{ + SQLInstanceName = $PSBoundParameters.SQLInstanceName + SQLServer = $PSBoundParameters.SQLServer + Database = $PSBoundParameters.Database + Name = $PSBoundParameters.Name + PermissionState = $PSBoundParameters.PermissionState + Permissions = $PSBoundParameters.Permissions + } - $getTargetResourceResult = Get-TargetResource @PSBoundParameters + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + <# + There is no need to evaluate the parameter Permissions here. + In the Get-TargetResource function there is a test to verify if Permissions is in + desired state. If the permissions are correct, then Get-TargetResource will return + the value 'Present' for the Ensure parameter, otherwise Ensure will have the value + 'Absent'. + #> return Test-SQLDscParameterState -CurrentValues $getTargetResourceResult ` -DesiredValues $PSBoundParameters ` - -ValuesToCheck @('Name', 'Ensure', 'PermissionState', 'Permissions') + -ValuesToCheck @('Name', 'Ensure', 'PermissionState') } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof index 5d58b58a1..01c4b8cb8 100644 --- a/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof +++ b/DSCResources/MSFT_xSQLServerDatabasePermission/MSFT_xSQLServerDatabasePermission.schema.mof @@ -4,7 +4,7 @@ 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; + [Key, Description("The state of the permission. Valid values are 'Grant' or 'Deny'."), ValueMap{"Grant","Deny","GrantWithGrant"}, Values{"Grant","Deny","GrantWithGrant"}] 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_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 index 4b9d90cde..0c377d6bc 100644 --- a/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 +++ b/DSCResources/MSFT_xSQLServerDatabaseRecoveryModel/MSFT_xSQLServerDatabaseRecoveryModel.psm1 @@ -1,5 +1,6 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Force - +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 @@ -22,54 +23,51 @@ function Get-TargetResource [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateSet('Full','Simple','BulkLogged')] + [ValidateNotNullOrEmpty()] [System.String] $RecoveryModel, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name ) - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - if ($sql) + if ($sqlServerObject) { Write-Verbose -Message "Getting RecoveryModel of SQL database '$Name'" - $sqlDatabase = $sql.Databases + $sqlDatabaseObject = $sqlServerObject.Databases[$Name] - if ($sqlDatabase) + if ($sqlDatabaseObject) { - 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 - } + $sqlDatabaseRecoveryModel = $sqlDatabaseObject.RecoveryModel + New-VerboseMessage -Message "The current recovery model used by database $Name is '$sqlDatabaseRecoveryModel'" } else { - New-WarningMessage -Message 'Failed getting SQL databases' - $null = $getSqlDatabaseRecoveryModel + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult } } $returnValue = @{ Name = $Name - RecoveryModel = $getSqlDatabaseRecoveryModel + RecoveryModel = $sqlDatabaseRecoveryModel SQLServer = $SQLServer SQLInstanceName = $SQLInstanceName } @@ -98,37 +96,49 @@ function Set-TargetResource [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateSet('Full','Simple','BulkLogged')] + [ValidateNotNullOrEmpty()] [System.String] $RecoveryModel, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name ) - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - if ($sql) + if ($sqlServerObject) { - $sqlDatabase = $Sql.Databases[$Name] - if ($sqlDatabase) + Write-Verbose -Message "Setting RecoveryModel of SQL database '$Name'" + $sqlDatabaseObject = $sqlServerObject.Databases[$Name] + + if ($sqlDatabaseObject) { - Write-Verbose -Message "Setting database '$Name' with RecoveryModel '$RecoveryModel'" - Set-SqlDatabaseRecoveryModel -SqlServerObject $sql -DatabaseName $Name -RecoveryModel $RecoveryModel + if($sqlDatabaseObject.RecoveryModel -ne $RecoveryModel) + { + $sqlDatabaseObject.RecoveryModel = $RecoveryModel + $sqlDatabaseObject.Alter() + New-VerboseMessage -Message "The recovery model for the database $Name is changed to '$RecoveryModel'." + } } else { - New-VerboseMessage -Message "SQL Database name $Name does not exist" + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` + -ErrorCategory InvalidResult } } } @@ -155,20 +165,24 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateSet('Full','Simple','BulkLogged')] + [ValidateNotNullOrEmpty()] [System.String] $RecoveryModel, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name ) @@ -177,7 +191,7 @@ function Test-TargetResource $currentValues = Get-TargetResource @PSBoundParameters - return Test-SQLDscParameterState -CurrentValues $CurrentValues ` + return Test-SQLDscParameterState -CurrentValues $currentValues ` -DesiredValues $PSBoundParameters ` -ValuesToCheck @('Name','RecoveryModel') } diff --git a/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.psm1 b/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.psm1 index d013d63a0..77d704b83 100644 --- a/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.psm1 +++ b/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.psm1 @@ -1,7 +1,6 @@ -$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 +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force <# .SYNOPSIS @@ -31,97 +30,105 @@ function Get-TargetResource [OutputType([System.Collections.Hashtable])] param ( - [ValidateSet('Present','Absent')] - [System.String] - $Ensure, - [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Database, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String[]] $Role ) - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + Write-Verbose -Message "Getting SQL Database role for $Name" - if ($sql) + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sqlServerObject) { # Check database exists - if ( !($sqlDatabase = $sql.Databases[$Database]) ) + if ( -not ($sqlDatabaseObject = $sqlServerObject.Databases[$Database]) ) { - throw New-TerminatingError -ErrorType NoDatabase -FormatArgs @($Database, $SQLServer, $SQLInstanceName) -ErrorCategory ObjectNotFound + throw New-TerminatingError -ErrorType NoDatabase ` + -FormatArgs @($Database, $SQLServer, $SQLInstanceName) ` + -ErrorCategory ObjectNotFound } # Check role exists foreach ($currentRole in $Role) { - if( !($sqlDatabase.Roles[$currentRole]) ) + if( -not ($sqlDatabaseObject.Roles[$currentRole]) ) { - throw New-TerminatingError -ErrorType RoleNotFound -FormatArgs @($currentRole, $Database, $SQLServer, $SQLInstanceName) -ErrorCategory ObjectNotFound + throw New-TerminatingError -ErrorType RoleNotFound ` + -FormatArgs @($currentRole, $Database, $SQLServer, $SQLInstanceName) ` + -ErrorCategory ObjectNotFound } } # Check login exists - if ( !($sql.Logins[$Name]) ) + if ( -not ($sqlServerObject.Logins[$Name]) ) { - throw New-TerminatingError -ErrorType LoginNotFound -FormatArgs @($Name, $SQLServer, $SQLInstanceName) -ErrorCategory ObjectNotFound + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($Name, $SQLServer, $SQLInstanceName) ` + -ErrorCategory ObjectNotFound } - $Ensure = 'Absent' + $ensure = 'Absent' $grantedRole = @() - if ($sqlDatabaseUser = $sqlDatabase.Users[$Name] ) + if ($sqlDatabaseUser = $sqlDatabaseObject.Users[$Name] ) { foreach ($currentRole in $Role) { if ($sqlDatabaseUser.IsMember($currentRole)) { - New-VerboseMessage -Message "The login '$Name' is a member of the role '$currentRole' on the database '$Database', on the instance $SQLServer\$SQLInstanceName" + New-VerboseMessage -Message ("The login '$Name' is a member of the role '$currentRole' on the " + ` + "database '$Database', on the instance $SQLServer\$SQLInstanceName") $grantedRole += $currentRole } else { - New-VerboseMessage -Message "The login '$Name' is not a member of the role '$currentRole' on the database '$Database', on the instance $SQLServer\$SQLInstanceName" + New-VerboseMessage -Message ("The login '$Name' is not a member of the role '$currentRole' on the " + ` + "database '$Database', on the instance $SQLServer\$SQLInstanceName") } } - if ( !(Compare-Object -ReferenceObject $Role -DifferenceObject $grantedRole) ) + if ( -not (Compare-Object -ReferenceObject $Role -DifferenceObject $grantedRole) ) { - $Ensure = 'Present' + $ensure = 'Present' } } else { - New-VerboseMessage -Message "The login '$Name' is not a user of the database '$Database' on the instance $SQLServer\$SQLInstanceName" + New-VerboseMessage -Message ("The login '$Name' is not a user of the database " + ` + "'$Database' on the instance $SQLServer\$SQLInstanceName") } } - else - { - throw New-TerminatingError -ErrorType NotConnectedToInstance -FormatArgs @($SQLServer, $SQLInstanceName) -ErrorCategory InvalidResult - } $returnValue = @{ - Ensure = $Ensure - Name = $Name - SQLServer = $SQLServer + Ensure = $ensure + Name = $Name + SQLServer = $SQLServer SQLInstanceName = $SQLInstanceName - Database = $Database - Role = $grantedRole + Database = $Database + Role = $grantedRole } $returnValue @@ -157,57 +164,69 @@ function Set-TargetResource [CmdletBinding()] param ( + [Parameter()] [ValidateSet('Present','Absent')] + [ValidateNotNullOrEmpty()] [System.String] $Ensure = 'Present', [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Database, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String[]] $Role ) - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + Write-Verbose -Message "Setting SQL Database role for $Name" + + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - if ($sql) + if ($sqlServerObject) { - $sqlDatabase = $sql.Databases[$Database] + $sqlDatabaseObject = $sqlServerObject.Databases[$Database] switch ($Ensure) { 'Present' { # Adding database user if it does not exist. - if ( !($sqlDatabase.Users[$Name]) ) + if ( -not ($sqlDatabaseObject.Users[$Name]) ) { try { - New-VerboseMessage -Message "Adding the login '$Name' as a user of the database '$Database', on the instance $SQLServer\$SQLInstanceName" + New-VerboseMessage -Message ("Adding the login '$Name' as a user of the database " + ` + "'$Database', on the instance $SQLServer\$SQLInstanceName") - $sqlDatabaseUser = New-Object Microsoft.SqlServer.Management.Smo.User $SQLDatabase, $Name + $sqlDatabaseUser = New-Object -TypeName Microsoft.SqlServer.Management.Smo.User ` + -ArgumentList $sqlDatabaseObject, $Name $sqlDatabaseUser.Login = $Name $sqlDatabaseUser.Create() } catch { - "Failed adding the login '$Name' as a user of the database '$Database', on the instance $SQLServer\$SQLInstanceName" - - throw $_ + throw New-TerminatingError -ErrorType AddLoginDatabaseSetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$Name,$Database) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception } } @@ -216,16 +235,18 @@ function Set-TargetResource { try { - New-VerboseMessage -Message "Adding the login '$Name' to the role '$currentRole' on the database '$Database', on the instance $SQLServer\$SQLInstanceName" + New-VerboseMessage -Message ("Adding the login '$Name' to the role '$currentRole' on the " + ` + "database '$Database', on the instance $SQLServer\$SQLInstanceName") - $sqlDatabaseRole = $sqlDatabase.Roles[$currentRole] + $sqlDatabaseRole = $sqlDatabaseObject.Roles[$currentRole] $sqlDatabaseRole.AddMember($Name) } catch { - New-VerboseMessage -Message "Failed adding the login '$Name' to the role '$currentRole' on the database '$Database', on the instance $SQLServer\$SQLInstanceName" - - throw $_ + throw New-TerminatingError -ErrorType AddMemberDatabaseSetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$Name,$Role,$Database) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception } } } @@ -236,26 +257,23 @@ function Set-TargetResource { foreach ($currentRole in $Role) { - New-VerboseMessage -Message "Removing the login '$Name' to the role '$currentRole' on the database '$Database', on the instance $SQLServer\$SQLInstanceName" + New-VerboseMessage -Message ("Removing the login '$Name' to the role '$currentRole' on the " + ` + "database '$Database', on the instance $SQLServer\$SQLInstanceName") - $sqlDatabaseRole = $sqlDatabase.Roles[$currentRole] + $sqlDatabaseRole = $sqlDatabaseObject.Roles[$currentRole] $sqlDatabaseRole.DropMember($Name) } } catch { - New-VerboseMessage -Message "Failed removing the login '$Name' from the role '$Role' on the database '$Database', on the instance $SQLServer\$SQLInstanceName" - - throw $_ + throw New-TerminatingError -ErrorType DropMemberDatabaseSetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$Name,$Role,$Database) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception } } } } - - if ( !(Test-TargetResource @PSBoundParameters) ) - { - throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult - } } <# @@ -286,32 +304,74 @@ function Test-TargetResource [OutputType([System.Boolean])] param ( + [Parameter()] [ValidateSet('Present','Absent')] + [ValidateNotNullOrEmpty()] [System.String] $Ensure = 'Present', [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $Database, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String[]] $Role ) - return (((Get-TargetResource @PSBoundParameters).Ensure) -eq $Ensure) + Write-Verbose -Message "Testing SQL Database role for $Name" + + $getTargetResourceParameters = @{ + SQLInstanceName = $PSBoundParameters.SQLInstanceName + SQLServer = $PSBoundParameters.SQLServer + Role = $PSBoundParameters.Role + Database = $PSBoundParameters.Database + Name = $PSBoundParameters.Name + } + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $isDatabaseRoleInDesiredState = $true + + switch ($Ensure) + { + 'Absent' + { + if ($getTargetResourceResult.Ensure -ne 'Absent') + { + New-VerboseMessage -Message "Ensure is set to Absent. The existing role for $Name should be dropped" + $isDatabaseRoleInDesiredState = $false + } + } + + 'Present' + { + if ($getTargetResourceResult.Ensure -ne 'Present') + { + New-VerboseMessage -Message "Ensure is set to Present. The missing role for $Name should be added" + $isDatabaseRoleInDesiredState = $false + } + } + } + + $isDatabaseRoleInDesiredState } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.schema.mof b/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.schema.mof index 764e5d56e..ef07952f9 100644 --- a/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.schema.mof +++ b/DSCResources/MSFT_xSQLServerDatabaseRole/MSFT_xSQLServerDatabaseRole.schema.mof @@ -1,12 +1,10 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerDatabaseRole")] class MSFT_xSQLServerDatabaseRole : OMI_BaseResource { [Write, Description("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)."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Key, Description("The name of the login that will become a member, or removed as a member, of the role(s).")] String Name; - [Key, Description("The SQL server on which the instance exist.")] String SQLServer; - [Key, Description("The SQL instance in which the database exist.")] String SQLInstanceName; + [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; [Key, Description("The database in which the login (user) and role(s) exist.")] String Database; [Required, Description("One or more roles to which the login (user) will be added or removed.")] String Role[]; }; - diff --git a/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.psm1 b/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.psm1 index f6bf87aa4..e72954b28 100644 --- a/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.psm1 +++ b/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.psm1 @@ -1,163 +1,281 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Verbose -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - -# DSC resource to manage SQL Endpoint - -# NOTE: This resource requires WMF5 and PsDscRunAsCredential - +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force +<# + .SYNOPSIS + Returns the current state of the endpoint. + + .PARAMETER EndpointName + The name of the endpoint. + + .PARAMETER SQLServer + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + .PARAMETER SQLInstanceName + The name of the SQL instance to be configured. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $EndPointName, - - [ValidateSet("Present","Absent")] - [System.String] - $Ensure, - - [System.UInt32] - $Port, - - [System.String] - $AuthorizedUser, + $EndpointName, + [Parameter()] [System.String] $SQLServer = $env:COMPUTERNAME, + [Parameter(Mandatory = $true)] [System.String] - $SQLInstanceName = "MSSQLSERVER" + $SQLInstanceName ) - - $vConfigured = Test-TargetResource -EndPointName $EndPointName -Ensure $Ensure -Port $Port -AuthorizedUser $AuthorizedUser - if(!$SQL) + + $getTargetResourceReturnValues = @{ + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + Ensure = 'Absent' + EndpointName = '' + Port = '' + IpAddress = '' + } + + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + if ($sqlServerObject) { - $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + Write-Verbose -Message ('Connected to {0}\{1}' -f $SQLServer, $SQLInstanceName) + + $endpointObject = $sqlServerObject.Endpoints[$EndpointName] + if ($endpointObject.Name -eq $EndpointName) + { + if ($sqlServerObject.Endpoints[$EndPointName].EndpointType -ne 'DatabaseMirroring') + { + throw New-TerminatingError -ErrorType EndpointFoundButWrongType ` + -FormatArgs @($EndpointName) ` + -ErrorCategory InvalidOperation + } + + $getTargetResourceReturnValues.Ensure = 'Present' + $getTargetResourceReturnValues.EndpointName = $endpointObject.Name + $getTargetResourceReturnValues.Port = $endpointObject.Protocol.Tcp.ListenerPort + $getTargetResourceReturnValues.IpAddress = $endpointObject.Protocol.Tcp.ListenerIPAddress + } + else + { + $getTargetResourceReturnValues.Ensure = 'Absent' + $getTargetResourceReturnValues.EndpointName = '' + $getTargetResourceReturnValues.Port = '' + $getTargetResourceReturnValues.IpAddress = '' + } } - - $returnValue = @{ - EndPointName = $EndPointName - Ensure = $vConfigured - Port = $sql.Endpoints[$EndPointName].Protocol.Tcp.ListenerPort - AuthorizedUser = $sql.Endpoints[$EndPointName].EnumObjectPermissions().grantee + else + { + throw New-TerminatingError -ErrorType NotConnectedToInstance ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidOperation } - $returnValue + return $getTargetResourceReturnValues } +<# + .SYNOPSIS + Create, changes or drops an endpoint. + .PARAMETER EndpointName + The name of the endpoint. + + .PARAMETER Ensure + If the endpoint should be present or absent. Default values is 'Present'. + + .PARAMETER Port + The network port the endpoint is listening on. Default value is 5022. + + .PARAMETER SQLServer + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + .PARAMETER SQLInstanceName + The name of the SQL instance to be configured. + + .PARAMETER IpAddress + The network IP address the endpoint is listening on. Defaults to '0.0.0.0' which means listen on any valid IP address. +#> function Set-TargetResource { [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $EndPointName, + $EndpointName, - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', - [System.UInt32] - $Port, + [Parameter()] + [System.UInt16] + $Port = 5022, + [Parameter()] [System.String] - $AuthorizedUser, + $SQLServer = $env:COMPUTERNAME, + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLInstanceName, + [Parameter()] [System.String] - $SQLInstanceName = "MSSQLSERVER" + $IpAddress = '0.0.0.0' ) - if(!$SQL) - { - $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - } -Write-Verbose "Connected to Server" + $getTargetResourceResult = Get-TargetResource -EndpointName $EndpointName -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - if($Ensure -eq "Present") + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + if ($sqlServerObject) { - Write-Verbose "Check to see login $AuthorizedUser exist on the server" - - if(!$SQL.Logins.Contains($AuthorizedUser)) + if ($Ensure -eq 'Present' -and $getTargetResourceResult.Ensure -eq 'Absent') + { + Write-Verbose -Message ('Creating endpoint {0}.' -f $EndpointName) + + $endpointObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Endpoint -ArgumentList $sqlServerObject, $EndpointName + $endpointObject.EndpointType = [Microsoft.SqlServer.Management.Smo.EndpointType]::DatabaseMirroring + $endpointObject.ProtocolType = [Microsoft.SqlServer.Management.Smo.ProtocolType]::Tcp + $endpointObject.Protocol.Tcp.ListenerPort = $Port + $endpointObject.Protocol.Tcp.ListenerIPAddress = $IpAddress + $endpointObject.Payload.DatabaseMirroring.ServerMirroringRole = [Microsoft.SqlServer.Management.Smo.ServerMirroringRole]::All + $endpointObject.Payload.DatabaseMirroring.EndpointEncryption = [Microsoft.SqlServer.Management.Smo.EndpointEncryption]::Required + $endpointObject.Payload.DatabaseMirroring.EndpointEncryptionAlgorithm = [Microsoft.SqlServer.Management.Smo.EndpointEncryptionAlgorithm]::Aes + $endpointObject.Create() + $endpointObject.Start() + } + elseif ($Ensure -eq 'Present' -and $getTargetResourceResult.Ensure -eq 'Present') { - throw New-TerminatingError -ErrorType NoAuthorizedUser -FormatArgs @($AuthorizedUser,$SQLServer,$SQLInstanceName) -ErrorCategory InvalidResult + # The endpoint already exist, verifying supported endpoint properties so they are in desired state. + $endpointObject = $sqlServerObject.Endpoints[$EndpointName] + if ($endpointObject) + { + if ($endpointObject.Protocol.Tcp.ListenerIPAddress -ne $IpAddress) + { + Write-Verbose -Message ('Updating endpoint {0} IP address to {1}.' -f $EndpointName, $IpAddress) + $endpointObject.Protocol.Tcp.ListenerIPAddress = $IpAddress + $endpointObject.Alter() + } + + if ($endpointObject.Protocol.Tcp.ListenerPort -ne $Port) + { + Write-Verbose -Message ('Updating endpoint {0} port to {1}.' -f $EndpointName, $Port) + $endpointObject.Protocol.Tcp.ListenerPort = $Port + $endpointObject.Alter() + } + } + else + { + throw New-TerminatingError -ErrorType EndpointNotFound -FormatArgs @($EndpointName) -ErrorCategory ObjectNotFound + } + } + elseif ($Ensure -eq 'Absent' -and $getTargetResourceResult.Ensure -eq 'Present') + { + Write-Verbose -Message ('Dropping endpoint {0}.' -f $EndpointName) + + $endpointObject = $sqlServerObject.Endpoints[$EndpointName] + if ($endpointObject) + { + $endpointObject.Drop() + } + else + { + throw New-TerminatingError -ErrorType EndpointNotFound -FormatArgs @($EndpointName) -ErrorCategory ObjectNotFound + } } - $Endpoint = New-Object -typename Microsoft.SqlServer.Management.Smo.Endpoint -ArgumentList $Sql,$EndpointName - $Endpoint.EndpointType = [Microsoft.SqlServer.Management.Smo.EndpointType]::DatabaseMirroring - $Endpoint.ProtocolType = [Microsoft.SqlServer.Management.Smo.ProtocolType]::Tcp - $Endpoint.Protocol.Tcp.ListenerPort = $Port - $Endpoint.Payload.DatabaseMirroring.ServerMirroringRole = [Microsoft.SqlServer.Management.Smo.ServerMirroringRole]::All - $Endpoint.Payload.DatabaseMirroring.EndpointEncryption = [Microsoft.SqlServer.Management.Smo.EndpointEncryption]::Required - $Endpoint.Payload.DatabaseMirroring.EndpointEncryptionAlgorithm = [Microsoft.SqlServer.Management.Smo.EndpointEncryptionAlgorithm]::Aes - $Endpoint.Create() - $Endpoint.Start() - $ConnectPerm = New-Object -TypeName Microsoft.SqlServer.Management.SMO.ObjectPermissionSet - $ConnectPerm.Connect= $true - $Endpoint.Grant($ConnectPerm,$AuthorizedUser) } - elseif($Ensure -eq "Absent") + else { - Write-Verbose "Drop $EndPointName" - $SQL.Endpoints[$EndPointName].Drop() + throw New-TerminatingError -ErrorType NotConnectedToInstance ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidOperation } } +<# + .SYNOPSIS + Tests if the principal (login) has the desired permissions. + + .PARAMETER EndpointName + The name of the endpoint. + + .PARAMETER Ensure + If the endpoint should be present or absent. Default values is 'Present'. + + .PARAMETER Port + The network port the endpoint is listening on. Default value is 5022. + .PARAMETER SQLServer + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + .PARAMETER SQLInstanceName + The name of the SQL instance to be configured. + + .PARAMETER IpAddress + The network IP address the endpoint is listening on. Defaults to '0.0.0.0' which means listen on any valid IP address. +#> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] - $EndPointName, + $EndpointName, - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', - [System.UInt32] - $Port, + [Parameter()] + [System.UInt16] + $Port = 5022, + [Parameter()] [System.String] - $AuthorizedUser, + $SQLServer = $env:COMPUTERNAME, + [Parameter(Mandatory = $true)] [System.String] - $SQLServer = $env:COMPUTERNAME, + $SQLInstanceName, + [Parameter()] [System.String] - $SQLInstanceName = "MSSQLSERVER" + $IpAddress = '0.0.0.0' ) - if(!$SQL) + $getTargetResourceResult = Get-TargetResource -EndpointName $EndpointName -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + if ($getTargetResourceResult.Ensure -eq $Ensure) { - $SQL = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + $result = $true + + if ($getTargetResourceResult.Ensure -eq 'Present' ` + -and ( + $getTargetResourceResult.Port -ne $Port ` + -or $getTargetResourceResult.IpAddress -ne $IpAddress + ) + ) + { + $result = $false + } } - - $result = [System.Boolean] - - - if(($sql.Endpoints[$EndPointName].Name -eq $EndPointName)-and($ensure -eq "Present") ) + else { - $Result = $true + $result = $false } - else - {$result = $false} - $result + return $result } - Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.schema.mof b/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.schema.mof index 85f4c2773..737bb8dbf 100644 --- a/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.schema.mof +++ b/DSCResources/MSFT_xSQLServerEndpoint/MSFT_xSQLServerEndpoint.schema.mof @@ -1,12 +1,10 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerEndpoint")] class MSFT_xSQLServerEndpoint : OMI_BaseResource { - [Key] String EndPointName; - [Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write] Uint32 Port; - [Write] String AuthorizedUser; - [Write] String SQLServer; - [Write] String SQLInstanceName; + [Key, Description("The name of the endpoint.")] String EndpointName; + [Write, Description("If the endpoint should be present or absent. Default values is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("The network port the endpoint is listening on. Default value is 5022.")] Uint16 Port; + [Write, Description("The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME.")] String SQLServer; + [Key, Description("The name of the SQL instance to be configured.")] String SQLInstanceName; + [Write, Description("The network IP address the endpoint is listening on. Default the endpoint will listen on any valid IP address.")] String IpAddress; }; - diff --git a/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.psm1 b/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.psm1 index e6596fc39..bfdd1f18a 100644 --- a/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.psm1 +++ b/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.psm1 @@ -1,8 +1,22 @@ -$ErrorActionPreference = "Stop" +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force +<# + .SYNOPSIS + Returns the current state of the permissions for the principal (login). -$script:currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -ErrorAction Stop + .PARAMETER InstanceName + The name of the SQL instance to be configured. + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Name + The name of the endpoint. + + .PARAMETER Principal + The login to which permission will be set. +#> function Get-TargetResource { [CmdletBinding()] @@ -11,7 +25,7 @@ function Get-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, [Parameter(Mandatory = $true)] [System.String] @@ -26,30 +40,40 @@ function Get-TargetResource $Principal ) - try { - $endpoint = Get-SQLAlwaysOnEndpoint -Name $Name -NodeName $NodeName -InstanceName $InstanceName -Verbose:$VerbosePreference - - if( $null -ne $endpoint ) { - New-VerboseMessage -Message "Enumerating permissions for Endpoint $Name" + try + { + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $endpointObject = $sqlServerObject.Endpoints[$Name] + if( $null -ne $endpointObject ) + { + New-VerboseMessage -Message "Enumerating permissions for endpoint $Name" $permissionSet = New-Object -Property @{ Connect = $True } -TypeName Microsoft.SqlServer.Management.Smo.ObjectPermissionSet - $endpointPermission = $endpoint.EnumObjectPermissions( $permissionSet ) | Where-Object { $_.PermissionState -eq "Grant" -and $_.Grantee -eq $Principal } - if( $endpointPermission.Count -ne 0 ) { - $Ensure = "Present" - $Permission = "CONNECT" - } else { - $Ensure = "Absent" - $Permission = "" + $endpointPermission = $endpointObject.EnumObjectPermissions( $permissionSet ) | Where-Object { $_.PermissionState -eq "Grant" -and $_.Grantee -eq $Principal } + if ($endpointPermission.Count -ne 0) + { + $Ensure = 'Present' + $Permission = 'CONNECT' + } + else + { + $Ensure = 'Absent' + $Permission = '' } - } else { + } + else + { throw New-TerminatingError -ErrorType EndpointNotFound -FormatArgs @($Name) -ErrorCategory ObjectNotFound } - } catch { - throw New-TerminatingError -ErrorType EndpointErrorVerifyExist -FormatArgs @($Name) -ErrorCategory ObjectNotFound -InnerException $_.Exception + } + catch + { + throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -FormatArgs @($Name) -ErrorCategory ObjectNotFound -InnerException $_.Exception } - $returnValue = @{ + return @{ InstanceName = [System.String] $InstanceName NodeName = [System.String] $NodeName Ensure = [System.String] $Ensure @@ -57,26 +81,47 @@ function Get-TargetResource Principal = [System.String] $Principal Permission = [System.String] $Permission } - - return $returnValue } +<# + .SYNOPSIS + Grants or revokes the permission for the the principal (login). + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Ensure + If the permission should be present or absent. Default value is 'Present'. + + .PARAMETER Name + The name of the endpoint. + + .PARAMETER Permission + The permission to set for the login. Valid value for permission are only CONNECT. + + .PARAMETER Principal + The permission to set for the login. +#> function Set-TargetResource { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, [Parameter(Mandatory = $true)] [System.String] $NodeName, - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', [Parameter(Mandatory = $true)] [System.String] @@ -86,7 +131,8 @@ function Set-TargetResource [System.String] $Principal, - [ValidateSet("CONNECT")] + [Parameter()] + [ValidateSet('CONNECT')] [System.String] $Permission ) @@ -97,34 +143,62 @@ function Set-TargetResource Name = [System.String] $Name Principal = [System.String] $Principal } - - $endPointPermissionState = Get-TargetResource @parameters - if( $null -ne $endPointPermissionState ) { - if( $endPointPermissionState.Ensure -ne $Ensure ) { - $endpoint = Get-SQLAlwaysOnEndpoint -Name $Name -NodeName $NodeName -InstanceName $InstanceName -Verbose:$VerbosePreference - if( $null -ne $endpoint ) { - $permissionSet = New-Object -Property @{ Connect = $True } -TypeName Microsoft.SqlServer.Management.Smo.ObjectPermissionSet - - if( $Ensure -eq "Present") { - if( ( $PSCmdlet.ShouldProcess( $Name, "Grant permission to $Principal on Endpoint" ) ) ) { - $endpoint.Grant($permissionSet, $Principal ) - } - } else { - if( ( $PSCmdlet.ShouldProcess( $Name, "Revoke permission to $Principal on Endpoint" ) ) ) { - $endpoint.Revoke($permissionSet, $Principal ) - } - } - } else { - throw New-TerminatingError -ErrorType EndpointNotFound -FormatArgs @($Name) -ErrorCategory ObjectNotFound + + $getTargetResourceResult = Get-TargetResource @parameters + if ($getTargetResourceResult.Ensure -ne $Ensure) + { + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $endpointObject = $sqlServerObject.Endpoints[$Name] + if ($null -ne $endpointObject) + { + $permissionSet = New-Object -Property @{ Connect = $True } -TypeName Microsoft.SqlServer.Management.Smo.ObjectPermissionSet + + if ($Ensure -eq 'Present') + { + New-VerboseMessage -Message "Grant permission to $Principal on endpoint $Name" + + $endpointObject.Grant($permissionSet, $Principal) + } + else + { + New-VerboseMessage -Message "Revoke permission to $Principal on endpoint $Name" + $endpointObject.Revoke($permissionSet, $Principal) } - } else { - New-VerboseMessage -Message "State is already $Ensure" } - } else { - throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult + else + { + throw New-TerminatingError -ErrorType EndpointNotFound -FormatArgs @($Name) -ErrorCategory ObjectNotFound + } + } + else + { + New-VerboseMessage -Message "State is already $Ensure" } } +<# + .SYNOPSIS + Tests if the principal (login) has the desired permissions. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Ensure + If the permission should be present or absent. Default value is 'Present'. + + .PARAMETER Name + The name of the endpoint. + + .PARAMETER Permission + The permission to set for the login. Valid value for permission are only CONNECT. + + .PARAMETER Principal + The permission to set for the login. +#> function Test-TargetResource { [CmdletBinding()] @@ -133,15 +207,16 @@ function Test-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, [Parameter(Mandatory = $true)] [System.String] $NodeName, - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', [Parameter(Mandatory = $true)] [System.String] @@ -151,7 +226,8 @@ function Test-TargetResource [System.String] $Principal, - [ValidateSet("CONNECT")] + [Parameter()] + [ValidateSet('CONNECT')] [System.String] $Permission ) @@ -162,20 +238,12 @@ function Test-TargetResource Name = [System.String] $Name Principal = [System.String] $Principal } - + New-VerboseMessage -Message "Testing state of endpoint permission for $Principal" - $endPointPermissionState = Get-TargetResource @parameters - if( $null -ne $endPointPermissionState ) { - [System.Boolean] $result = $false - if( $endPointPermissionState.Ensure -eq $Ensure) { - $result = $true - } - } else { - throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult - } + $getTargetResourceResult = Get-TargetResource @parameters - return $result + return $getTargetResourceResult.Ensure -eq $Ensure } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.schema.mof b/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.schema.mof index 1c8ee3728..d50707093 100644 --- a/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.schema.mof +++ b/DSCResources/MSFT_xSQLServerEndpointPermission/MSFT_xSQLServerEndpointPermission.schema.mof @@ -2,11 +2,11 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerEndpointPermission")] class MSFT_xSQLServerEndpointPermission : OMI_BaseResource { - [Key, Description("The SQL Server instance name.")] String InstanceName; - [Required, Description("The host name or FQDN.")] String NodeName; - [Write, Description("If the permission should be present or absent."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("The name of the SQL instance to be configured.")] String InstanceName; + [Required, Description("The host name of the SQL Server to be configured.")] String NodeName; + [Write, Description("If the permission should be present or absent. Default value is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Required, Description("The name of the endpoint.")] String Name; [Key, Description("The login to which permission will be set.")] String Principal; - [Write, Description("The permission to set for the login."), ValueMap{"CONNECT"}, Values{"CONNECT"}] String Permission; + [Write, Description("The permission to set for the login. Valid value for permission are only CONNECT."), ValueMap{"CONNECT"}, Values{"CONNECT"}] String Permission; }; diff --git a/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.psm1 b/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.psm1 index 4dfb3f1a2..55a630c1e 100644 --- a/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.psm1 +++ b/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.psm1 @@ -1,8 +1,19 @@ -$ErrorActionPreference = "Stop" - -$script:currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -ErrorAction Stop - +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force +<# + .SYNOPSIS + Returns the current state of an endpoint. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Name + The name of the endpoint. +#> function Get-TargetResource { [CmdletBinding()] @@ -11,11 +22,11 @@ function Get-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [System.String] - $NodeName, + $NodeName = $env:COMPUTERNAME, [Parameter(Mandatory = $true)] [System.String] @@ -23,81 +34,124 @@ function Get-TargetResource ) New-VerboseMessage -Message "Getting state of endpoint $Name" - - try { - $endpoint = Get-SQLAlwaysOnEndpoint -Name $Name -NodeName $NodeName -InstanceName $InstanceName -Verbose:$VerbosePreference - - if( $null -ne $endpoint ) { - $state = $endpoint.EndpointState - } else { + + try + { + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $endpointObject = $sqlServerObject.Endpoints[$Name] + if ($null -ne $endpointObject) + { + $currentState = $endpointObject.EndpointState + } + else + { throw New-TerminatingError -ErrorType EndpointNotFound -FormatArgs @($Name) -ErrorCategory ObjectNotFound } - } catch { + } + catch + { throw New-TerminatingError -ErrorType EndpointErrorVerifyExist -FormatArgs @($Name) -ErrorCategory ObjectNotFound -InnerException $_.Exception } - $returnValue = @{ + return @{ InstanceName = [System.String] $InstanceName NodeName = [System.String] $NodeName Name = [System.String] $Name - State = [System.String] $state + State = [System.String] $currentState } - - return $returnValue } +<# + .SYNOPSIS + Changes the state of an endpoint. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Name + The name of the endpoint. + + .PARAMETER State + The state of the endpoint. Valid states are Started, Stopped or Disabled. Default value is 'Started'. +#> function Set-TargetResource { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [System.String] - $NodeName, + $NodeName = $env:COMPUTERNAME, [Parameter(Mandatory = $true)] [System.String] $Name, - [ValidateSet("Started","Stopped","Disabled")] + [Parameter()] + [ValidateSet('Started','Stopped','Disabled')] [System.String] - $State + $State = 'Started' ) - + $parameters = @{ InstanceName = [System.String] $InstanceName NodeName = [System.String] $NodeName Name = [System.String] $Name } - - $endPointState = Get-TargetResource @parameters - if( $null -ne $endPointState ) { - if( $endPointState.State -ne $State ) { - if( ( $PSCmdlet.ShouldProcess( $Name, "Changing state of Endpoint" ) ) ) { - $endpoint = Get-SQLAlwaysOnEndpoint -Name $Name -NodeName $NodeName -InstanceName $InstanceName -Verbose:$VerbosePreference - $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName - - $setEndPointParams = @{ - Path = "SQLSERVER:\SQL\$NodeName\$InstanceName\Endpoints\$Name" - Port = $endpoint.Protocol.Tcp.ListenerPort - IpAddress = $endpoint.Protocol.Tcp.ListenerIPAddress.IPAddressToString - State = $State - } - - Set-SqlHADREndpoint @setEndPointParams -Verbose:$False | Out-Null # Suppressing Verbose because it prints the entire T-SQL statement otherwise + + $getTargetResourceResult = Get-TargetResource @parameters + if ($null -ne $getTargetResourceResult) + { + if ($getTargetResourceResult.State -ne $State) + { + New-VerboseMessage -Message ('Changing state of endpoint ''{0}''' -f $Name) + + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $endpointObject = $sqlServerObject.Endpoints[$Name] + + $setEndpointParams = @{ + InputObject = $endpointObject + State = $State } - } else { - New-VerboseMessage -Message "Endpoint configuration is already correct." + + Set-SqlHADREndpoint @setEndpointParams -ErrorAction Stop | Out-Null + } + else + { + New-VerboseMessage -Message ('Endpoint ''{0}'' state is already correct.' -f $Name) } - } else { + } + else + { throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult } } +<# + .SYNOPSIS + Tests the state of an endpoint if it is in desired state. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Name + The name of the endpoint. + + .PARAMETER State + The state of the endpoint. Valid states are Started, Stopped or Disabled. Default value is 'Started'. +#> function Test-TargetResource { [CmdletBinding()] @@ -106,36 +160,42 @@ function Test-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [System.String] - $NodeName, + $NodeName = $env:COMPUTERNAME, [Parameter(Mandatory = $true)] [System.String] $Name, - [ValidateSet("Started","Stopped","Disabled")] + [Parameter()] + [ValidateSet('Started','Stopped','Disabled')] [System.String] - $State + $State = 'Started' ) $parameters = @{ - InstanceName = [System.String] $InstanceName - NodeName = [System.String] $NodeName - Name = [System.String] $Name + InstanceName = $InstanceName + NodeName = $NodeName + Name = $Name } - New-VerboseMessage -Message "Testing state $State on endpoint $Name" - - $endPointState = Get-TargetResource @parameters - if( $null -ne $endPointState ) { - [System.Boolean] $result = $false - if( $endPointState.State -eq $State ) { + New-VerboseMessage -Message "Testing state $State on endpoint '$Name'" + + $getTargetResourceResult = Get-TargetResource @parameters + if ($null -ne $getTargetResourceResult) + { + $result = $false + + if ($getTargetResourceResult.State -eq $State) + { $result = $true } - } else { + } + else + { throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult } diff --git a/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.schema.mof b/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.schema.mof index 72d8294fc..14edfab02 100644 --- a/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.schema.mof +++ b/DSCResources/MSFT_xSQLServerEndpointState/MSFT_xSQLServerEndpointState.schema.mof @@ -1,10 +1,8 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerEndpointState")] class MSFT_xSQLServerEndpointState : OMI_BaseResource { - [Key, Description("The SQL Server instance name.")] String InstanceName; - [Required, Description("The host name or FQDN.")] String NodeName; - [Required, Description("The name of the endpoint.")] String Name; - [Write, Description("The state of the endpoint. Valid states are Started, Stopped or Disabled."), ValueMap{"Started","Stopped","Disabled"}, Values{"Started","Stopped","Disabled"}] String State; + [Key, Description("The name of the SQL instance to be configured.")] String InstanceName; + [Write, Description("The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME.")] String NodeName; + [Key, Description("The name of the endpoint.")] String Name; + [Write, Description("The state of the endpoint. Valid states are Started, Stopped or Disabled. Default value is 'Started'."), ValueMap{"Started","Stopped","Disabled"}, Values{"Started","Stopped","Disabled"}] String State; }; - diff --git a/DSCResources/MSFT_xSQLServerFailoverClusterSetup/MSFT_xSQLServerFailoverClusterSetup.schema.mof b/DSCResources/MSFT_xSQLServerFailoverClusterSetup/MSFT_xSQLServerFailoverClusterSetup.schema.mof index 1de6c3315..bb1ecd60b 100644 --- a/DSCResources/MSFT_xSQLServerFailoverClusterSetup/MSFT_xSQLServerFailoverClusterSetup.schema.mof +++ b/DSCResources/MSFT_xSQLServerFailoverClusterSetup/MSFT_xSQLServerFailoverClusterSetup.schema.mof @@ -36,7 +36,7 @@ class MSFT_xSQLServerFailoverClusterSetup : OMI_BaseResource [Write, Description("Path for SQL TempDB files.")] String SQLTempDBDir; [Write, Description("Path for SQL TempDB log files.")] String SQLTempDBLogDir; [Write, Description("Path for SQL backup files.")] String SQLBackupDir; - [Write, EmbeddedInstance("MSFT_Credential"), Description("Service account for Analysus Services service.")] String ASSvcAccount; + [Write, EmbeddedInstance("MSFT_Credential"), Description("Service account for Analysis Services service.")] String ASSvcAccount; [Read, Description("Output username for the Analysis Services service.")] String ASSvcAccountUsername; [Write, Description("Collation for Analysis Services.")] String ASCollation; [Write, Description("Array of accounts to be made Analysis Services admins.")] String ASSysAdminAccounts[]; diff --git a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 index cca1c76fb..c7b6f20d5 100644 --- a/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 +++ b/DSCResources/MSFT_xSQLServerFirewall/MSFT_xSQLServerFirewall.psm1 @@ -82,7 +82,7 @@ function Get-TargetResource $integrationServiceName = 'MsDtsServer{0}0' -f $sqlVersion $browserServiceName = 'SQLBrowser' - $ensure = 'Absent' + $ensure = 'Present' $featuresInstalled = '' $services = Get-Service @@ -111,7 +111,6 @@ function Get-TargetResource if (Test-IsFirewallRuleInDesiredState @databaseEngineFirewallRuleParameters) { $databaseEngineFirewall = $true - $ensure = 'Present' } else { @@ -131,7 +130,6 @@ function Get-TargetResource if (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters) { $browserFirewall = $true - $ensure = 'Present' } else { @@ -176,7 +174,6 @@ function Get-TargetResource -and (Test-IsFirewallRuleInDesiredState @reportingServicesSslFirewallRuleParameters)) { $reportingServicesFirewall = $true - $ensure = 'Present' } else { @@ -204,7 +201,6 @@ function Get-TargetResource if (Test-IsFirewallRuleInDesiredState @analysisServicesFirewallRuleParameters) { $analysisServicesFirewall = $true - $ensure = 'Present' } else { @@ -224,7 +220,6 @@ function Get-TargetResource if (Test-IsFirewallRuleInDesiredState @browserFirewallRuleParameters) { $browserFirewall = $true - $ensure = 'Present' } else { @@ -267,7 +262,6 @@ function Get-TargetResource -and (Test-IsFirewallRuleInDesiredState @integrationServicesFirewallRulePortParameters)) { $integrationServicesFirewall = $true - $ensure = 'Present' } else { @@ -278,6 +272,17 @@ function Get-TargetResource } } + if ( + ($Features -match 'SQLENGINE' -and -not ($databaseEngineFirewall -and $browserFirewall)) ` + -or ($Features -match 'RS' -and -not $reportingServicesFirewall) ` + -or ($Features -match 'AS' -and -not ($analysisServicesFirewall -and $browserFirewall)) ` + -or ($Features -match 'IS' -and -not $integrationServicesFirewall) + ) + { + $ensure = 'Absent' + } + + $featuresInstalled = $featuresInstalled.Trim(',') return @{ diff --git a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 index 3bbd95ae1..a00855744 100644 --- a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 +++ b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.psm1 @@ -8,12 +8,12 @@ Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Pare .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. + Name of the SQL instance to retrieve the login from. #> function Get-TargetResource { @@ -33,7 +33,7 @@ function Get-TargetResource [System.String] $SQLInstanceName ) - + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName Write-Verbose 'Getting SQL logins' @@ -58,6 +58,7 @@ function Get-TargetResource LoginType = $login.LoginType SQLServer = $SQLServer SQLInstanceName = $SQLInstanceName + Disabled = $login.IsDisabled } if ( $login.LoginType -eq 'SqlLogin' ) @@ -76,16 +77,16 @@ function Get-TargetResource .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. @@ -100,6 +101,9 @@ function Get-TargetResource .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. + + .PARAMETER Disabled + Specifies if the login is disabled. Default is $false. #> function Set-TargetResource { @@ -141,20 +145,24 @@ function Set-TargetResource $LoginCredential, [Parameter()] - [bool] + [System.Boolean] $LoginMustChangePassword = $true, [Parameter()] - [bool] + [System.Boolean] $LoginPasswordExpirationEnabled = $true, [Parameter()] - [bool] - $LoginPasswordPolicyEnforced = $true + [System.Boolean] + $LoginPasswordPolicyEnforced = $true, + + [Parameter()] + [System.Boolean] + $Disabled ) - + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - + switch ( $Ensure ) { 'Present' @@ -164,9 +172,7 @@ function Set-TargetResource $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." @@ -187,6 +193,19 @@ function Set-TargetResource Set-SQLServerLoginPassword -Login $login -SecureString $LoginCredential.Password } } + + if ( $PSBoundParameters.ContainsKey('Disabled') -and ($login.IsDisabled -ne $Disabled) ) + { + New-VerboseMessage -Message "Setting IsDisabled to '$Disabled' for the login '$Name' on the '$SQLServer\$SQLInstanceName' instance." + if( $Disabled ) + { + $login.Disable() + } + else + { + $login.Enable() + } + } } else { @@ -200,9 +219,9 @@ function Set-TargetResource { 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 @@ -215,7 +234,7 @@ function Set-TargetResource { throw New-TerminatingError -ErrorType IncorrectLoginMode -FormatArgs $SQLServer,$SQLInstanceName,$serverObject.LoginMode -ErrorCategory NotImplemented } - + $login.PasswordPolicyEnforced = $LoginPasswordPolicyEnforced $login.PasswordExpirationEnabled = $LoginPasswordExpirationEnabled if ( $LoginMustChangePassword ) @@ -227,7 +246,7 @@ function Set-TargetResource $LoginCreateOptions = [Microsoft.SqlServer.Management.Smo.LoginCreateOptions]::None } - New-SQLServerLogin -Login $login -LoginCreateOptions $LoginCreateOptions -SecureString $LoginCredential.Password -ErrorAction Stop + New-SQLServerLogin -Login $login -LoginCreateOptions $LoginCreateOptions -SecureString $LoginCredential.Password -ErrorAction Stop } default @@ -235,6 +254,12 @@ function Set-TargetResource New-SQLServerLogin -Login $login } } + + # we can only disable the login once it's been created + if( $Disabled ) + { + $login.Disable() + } } } @@ -255,16 +280,16 @@ function Set-TargetResource .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. @@ -279,6 +304,9 @@ function Set-TargetResource .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. + + .PARAMETER Disabled + Specifies if the login is disabled. Default is $false. #> function Test-TargetResource { @@ -320,21 +348,25 @@ function Test-TargetResource $LoginCredential, [Parameter()] - [bool] + [System.Boolean] $LoginMustChangePassword = $true, [Parameter()] - [bool] + [System.Boolean] $LoginPasswordExpirationEnabled = $true, [Parameter()] - [bool] - $LoginPasswordPolicyEnforced = $true + [System.Boolean] + $LoginPasswordPolicyEnforced = $true, + + [Parameter()] + [System.Boolean] + $Disabled ) # Assume the test will pass $testPassed = $true - + $getParams = @{ Name = $Name SQLServer = $SQLServer @@ -357,6 +389,12 @@ function Test-TargetResource $testPassed = $false } + if ( $PSBoundParameters.ContainsKey('Disabled') -and ($loginInfo.Disabled -ne $Disabled) ) + { + New-VerboseMessage -Message "The login '$Name' on the instance '$SQLServer\$SQLInstanceName' has IsDisabled set to $($loginInfo.Disabled) rather than $Disabled" + $testPassed = $false + } + if ( $LoginType -eq 'SqlLogin' ) { if ( $LoginPasswordExpirationEnabled -ne $loginInfo.LoginPasswordExpirationEnabled ) @@ -375,10 +413,10 @@ function Test-TargetResource if ( $testPassed -and $LoginCredential ) { $userCred = [System.Management.Automation.PSCredential]::new($Name, $LoginCredential.Password) - + try { - $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName -SetupCredential $userCred + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName -SetupCredential $userCred } catch { @@ -388,7 +426,7 @@ function Test-TargetResource } } } - + return $testPassed } @@ -415,7 +453,7 @@ function Update-SQLServerLogin { $originalErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' - + $Login.Alter() } catch @@ -471,14 +509,14 @@ function New-SQLServerLogin switch ( $PSCmdlet.ParameterSetName ) { - 'SqlLogin' - { + 'SqlLogin' + { try { $originalErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' - - $login.Create($SecureString,$LoginCreateOptions) + + $login.Create($SecureString,$LoginCreateOptions) } catch [Microsoft.SqlServer.Management.Smo.FailedOperationException] { @@ -507,7 +545,7 @@ function New-SQLServerLogin { $originalErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' - + $login.Create() } catch @@ -545,7 +583,7 @@ function Remove-SQLServerLogin { $originalErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' - + $Login.Drop() } catch @@ -588,7 +626,7 @@ function Set-SQLServerLoginPassword { $originalErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' - + $Login.ChangePassword($SecureString) } catch [Microsoft.SqlServer.Management.Smo.FailedOperationException] diff --git a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof index a8866341f..51f15fc66 100644 --- a/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof +++ b/DSCResources/MSFT_xSQLServerLogin/MSFT_xSQLServerLogin.schema.mof @@ -12,4 +12,5 @@ class MSFT_xSQLServerLogin : OMI_BaseResource [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; + [Write, Description("Specifies if the login is disabled. Default is $false.")] Boolean Disabled; }; diff --git a/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.psm1 b/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.psm1 index 06a1a7ca7..1ec443bbc 100644 --- a/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.psm1 +++ b/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.psm1 @@ -201,8 +201,8 @@ function Test-TargetResource Write-Verbose -Message 'Testing the max degree of parallelism server configuration option' $parameters = @{ - SQLInstanceName = $PSBoundParameters.SQLInstanceName - SQLServer = $PSBoundParameters.SQLServer + SQLInstanceName = $SQLInstanceName + SQLServer = $SQLServer } $currentValues = Get-TargetResource @parameters diff --git a/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.schema.mof b/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.schema.mof index 0ca9038ac..c89512c3e 100644 --- a/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.schema.mof +++ b/DSCResources/MSFT_xSQLServerMaxDop/MSFT_xSQLServerMaxDop.schema.mof @@ -1,9 +1,9 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerMaxDop")] class MSFT_xSQLServerMaxDop : OMI_BaseResource { - [Write, Description("An enumerated value that describes if MaxDop is configured (Present) or reset to default value (Absent)"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("Flag to Dynamically allocate Maxdop based on Best Practices")] Boolean DynamicAlloc; - [Write, Description("Numeric value to configure Maxdop to")] Sint32 MaxDop; - [Write, Description("The host name of the SQL Server to be configured. Default value is '$env:COMPUTERNAME'.")] String SQLServer; + [Write, Description("When set to 'Present' then max degree of parallelism will be set to either the value in parameter MaxDop or dynamically configured when parameter DynamicAlloc is set to $true. When set to 'Absent' max degree of parallelism will be set to 0 which means no limit in number of processors used in parallel plan execution."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("If set to $true then max degree of parallelism will be dynamically configured. When this is set parameter is set to $true, the parameter MaxDop must be set to $null or not be configured.")] Boolean DynamicAlloc; + [Write, Description("A numeric value to limit the number of processors used in parallel plan execution.")] Sint32 MaxDop; + [Write, Description("The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME.")] String SQLServer; [Key, Description("The name of the SQL instance to be configured.")] String SQLInstanceName; }; diff --git a/DSCResources/MSFT_xSQLServerMemory/MSFT_xSQLServerMemory.psm1 b/DSCResources/MSFT_xSQLServerMemory/MSFT_xSQLServerMemory.psm1 index 9814ef450..38d5f194c 100644 --- a/DSCResources/MSFT_xSQLServerMemory/MSFT_xSQLServerMemory.psm1 +++ b/DSCResources/MSFT_xSQLServerMemory/MSFT_xSQLServerMemory.psm1 @@ -229,8 +229,8 @@ function Test-TargetResource Write-Verbose -Message 'Testing the values of the minimum and maximum memory server configuration option set to be used by the instance.' $getTargetResourceParameters = @{ - SQLInstanceName = $PSBoundParameters.SQLInstanceName - SQLServer = $PSBoundParameters.SQLServer + SQLInstanceName = $SQLInstanceName + SQLServer = $SQLServer } $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters diff --git a/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.psm1 b/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.psm1 index 5c8fb2e25..679cdd472 100644 --- a/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.psm1 +++ b/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.psm1 @@ -1,287 +1,377 @@ -Function Get-TargetResource +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force +<# + .SYNOPSIS + Returns the current state of the SQL Server network properties. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER ProtocolName + The name of network protocol to be configured. Only tcp is currently supported. +#> +Function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] - param( - [parameter(Mandatory = $true)] + param + ( + [Parameter(Mandatory = $true)] [System.String] $InstanceName, - # for now support is just for tcp protocol - # possible future feature to support additional protocols - [parameter(Mandatory = $true)] - [ValidateSet("tcp")] + # For now there is only support for the tcp protocol. + [Parameter(Mandatory = $true)] + [ValidateSet('Tcp')] [System.String] $ProtocolName ) - Write-Verbose "xSQLServerNetwork.Get-TargetResourece ..." - Write-Verbose "Parameters: InstanceName = $InstanceName; ProtocolName = $ProtocolName" - - # create isolated appdomain to load version specific libs, this needed if you have multiple versions of SQL server in the same configuration - $dom_get = [System.AppDomain]::CreateDomain("xSQLServerNetwork_Get_$InstanceName") - - Try + try { - $version = GetVersion -InstanceName $InstanceName - - if([string]::IsNullOrEmpty($version)) - { - throw "Unable to resolve SQL version for instance" - } - - $smo = $dom_get.Load("Microsoft.SqlServer.Smo, Version=$version.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91") - $sqlWmiManagement = $dom_get.Load("Microsoft.SqlServer.SqlWmiManagement, Version=$version.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91") + $applicationDomainObject = Register-SqlWmiManagement -SQLInstanceName $InstanceName - Write-Verbose "Creating [Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer] object" - $wmi = new-object $sqlWmiManagement.GetType("Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer") + $managedComputerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer - Write-Verbose "Getting [$ProtocolName] network protocol for [$InstanceName] SQL instance" - $tcp = $wmi.ServerInstances[$InstanceName].ServerProtocols[$ProtocolName] + Write-Verbose "Getting network protocol [$ProtocolName] for SQL instance [$InstanceName]." + $tcp = $managedComputerObject.ServerInstances[$InstanceName].ServerProtocols[$ProtocolName] - Write-Verbose "Reading state values:" + Write-Verbose "Reading current network properties." $returnValue = @{ InstanceName = $InstanceName ProtocolName = $ProtocolName IsEnabled = $tcp.IsEnabled - TCPDynamicPorts = $tcp.IPAddresses["IPAll"].IPAddressProperties["TcpDynamicPorts"].Value - TCPPort = $tcp.IPAddresses["IPAll"].IPAddressProperties["TcpPort"].Value + TcpDynamicPorts = $tcp.IPAddresses['IPAll'].IPAddressProperties['TcpDynamicPorts'].Value + TcpPort = $tcp.IPAddresses['IPAll'].IPAddressProperties['TcpPort'].Value } - $returnValue.Keys | % { Write-Verbose "$_ = $($returnValue[$_])" } - + $returnValue.Keys | ForEach-Object { + Write-Verbose "$_ = $($returnValue[$_])" + } } - Finally + finally { - [System.AppDomain]::Unload($dom_get) + Unregister-SqlAssemblies -ApplicationDomain $applicationDomainObject } - + return $returnValue } +<# + .SYNOPSIS + Sets the SQL Server network properties. + + .PARAMETER SQLServer + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER ProtocolName + The name of network protocol to be configured. Only tcp is currently supported. + + .PARAMETER IsEnabled + Enables or disables the network protocol. + + .PARAMETER TcpDynamicPorts + Set the value to '0' if dynamic ports should be used. + If static port should be used set this to a empty string value. + Value can not be set to '0' if TcpPort is also set to a value. + + .PARAMETER TcpPort + The TCP port(s) that SQL Server should be listening on. + If the IP address should listen on more than one port, list all ports + separated with a comma ('1433,1500,1501'). To use this parameter set + TcpDynamicPorts to the value '' (empty string). + + .PARAMETER RestartService + If set to $true then SQL Server and dependent services will be restarted + if a change to the configuration is made. The default value is $false. + + .PARAMETER RestartTimeout + Timeout value for restarting the SQL Server services. The default value + is 120 seconds. +#> Function Set-TargetResource { [CmdletBinding()] - param( - [parameter(Mandatory = $true)] + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] [System.String] $InstanceName, - [parameter(Mandatory = $true)] - [ValidateSet("tcp")] + [Parameter(Mandatory = $true)] + [ValidateSet('Tcp')] [System.String] $ProtocolName, + [Parameter()] [System.Boolean] $IsEnabled, - [ValidateSet("0")] + [Parameter()] + [ValidateSet('0','')] [System.String] - $TCPDynamicPorts, + $TcpDynamicPorts, + [Parameter()] [System.String] - $TCPPort, + $TcpPort, + [Parameter()] [System.Boolean] - $RestartService = $false - ) + $RestartService = $false, - Write-Verbose "xSQLServerNetwork.Set-TargetResource ..." - Write-Verbose "Parameters: InstanceName = $InstanceName; ProtocolName = $ProtocolName; IsEnabled=$IsEnabled; TCPDynamicPorts = $TCPDynamicPorts; TCPPort = $TCPPort; RestartService=$RestartService;" + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 + ) - Write-Verbose "Calling xSQLServerNetwork.Get-TargetResource ..." - $currentState = Get-TargetResource -InstanceName $InstanceName -ProtocolName $ProtocolName + if ($TcpDynamicPorts -eq '0' -and $TcpPort) + { + throw New-TerminatingError -ErrorType UnableToUseBothDynamicAndStaticPort -ErrorCategory InvalidOperation + } - # create isolated appdomain to load version specific libs, this needed if you have multiple versions of SQL server in the same configuration - $dom_set = [System.AppDomain]::CreateDomain("xSQLServerNetwork_Set_$InstanceName") + $getTargetResourceResult = Get-TargetResource -InstanceName $InstanceName -ProtocolName $ProtocolName - Try + try { - $version = GetVersion -InstanceName $InstanceName - - if([string]::IsNullOrEmpty($version)) - { - throw "Unable to resolve SQL version for instance" - } - - $smo = $dom_set.Load("Microsoft.SqlServer.Smo, Version=$version.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91") - $sqlWmiManagement = $dom_set.Load("Microsoft.SqlServer.SqlWmiManagement, Version=$version.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91") + $applicationDomainObject = Register-SqlWmiManagement -SQLInstanceName $InstanceName $desiredState = @{ InstanceName = $InstanceName ProtocolName = $ProtocolName IsEnabled = $IsEnabled - TCPDynamicPorts = $TCPDynamicPorts - TCPPort = $TCPPort + TcpDynamicPorts = $TcpDynamicPorts + TcpPort = $TcpPort } - Write-Verbose "Creating [Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer] object" - $wmi = new-object $sqlWmiManagement.GetType("Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer") + $isRestartNeeded = $false - Write-Verbose "Getting [$ProtocolName] network protocol for [$InstanceName] SQL instance" - $tcp = $wmi.ServerInstances[$InstanceName].ServerProtocols[$ProtocolName] + $managedComputerObject = New-Object Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer - Write-Verbose "Checking [IsEnabled] property ..." - if($desiredState["IsEnabled"] -ine $currentState["IsEnabled"]) - { - Write-Verbose "Updating [IsEnabled] from $($currentState["IsEnabled"]) to $($desiredState["IsEnabled"])" - $tcp.IsEnabled = $desiredState["IsEnabled"] - } + Write-Verbose "Getting [$ProtocolName] network protocol for [$InstanceName] SQL instance" + $tcp = $managedComputerObject.ServerInstances[$InstanceName].ServerProtocols[$ProtocolName] - Write-Verbose "Checking [TCPDynamicPorts] property ..." - if($desiredState["TCPDynamicPorts"] -ine $currentState["TCPDynamicPorts"]) + Write-Verbose 'Checking [IsEnabled] property.' + if ($desiredState.IsEnabled -ine $getTargetResourceResult.IsEnabled) { - Write-Verbose "Updating [TCPDynamicPorts] from $($currentState["TCPDynamicPorts"]) to $($desiredState["TCPDynamicPorts"])" - $tcp.IPAddresses["IPAll"].IPAddressProperties["TcpDynamicPorts"].Value = $desiredState["TCPDynamicPorts"] - } + Write-Verbose "Updating [IsEnabled] from $($getTargetResourceResult.IsEnabled) to $($desiredState.IsEnabled)." + $tcp.IsEnabled = $desiredState.IsEnabled + $tcp.Alter() - Write-Verbose "Checking [TCPPort property] ..." - if($desiredState["TCPPort"] -ine $currentState["TCPPort"]) - { - Write-Verbose "Updating [TCPPort] from $($currentState["TCPPort"]) to $($desiredState["TCPPort"])" - $tcp.IPAddresses["IPAll"].IPAddressProperties["TcpPort"].Value = $desiredState["TCPPort"] + $isRestartNeeded = $true } - Write-Verbose "Saving changes ..." - $tcp.Alter() - - if($RestartService) + Write-Verbose 'Checking [TcpDynamicPorts] property.' + if ($desiredState.TcpDynamicPorts -ine $getTargetResourceResult.TcpDynamicPorts) { - Write-Verbose "SQL Service will be restarted ..." - if($InstanceName -eq "MSSQLSERVER") + $fromTcpDynamicPortsValue = $getTargetResourceResult.TcpDynamicPorts + if ($fromTcpDynamicPortsValue -eq '') { - $dbServiceName = "MSSQLSERVER" - $agtServiceName = "SQLSERVERAGENT" + $fromTcpDynamicPortsValue = 'none' } - else + + $toTcpDynamicPortsValue = $desiredState.TcpDynamicPorts + if ($toTcpDynamicPortsValue -eq '') { - $dbServiceName = "MSSQL`$$InstanceName" - $agtServiceName = "SQLAgent`$$InstanceName" + $toTcpDynamicPortsValue = 'none' } - $sqlService = $wmi.Services[$dbServiceName] - $agentService = $wmi.Services[$agtServiceName] - $startAgent = ($agentService.ServiceState -eq "Running") + Write-Verbose "Updating [TcpDynamicPorts] from $($fromTcpDynamicPortsValue) to $($toTcpDynamicPortsValue)." + $tcp.IPAddresses['IPAll'].IPAddressProperties['TcpDynamicPorts'].Value = $desiredState.TcpDynamicPorts + $tcp.Alter() - if ($sqlService -eq $null) - { - throw "$dbServiceName service was not found, restart service failed" - } - - Write-Verbose "Stopping [$dbServiceName] service ..." - $sqlService.Stop() + $isRestartNeeded = $true + } - while($sqlService.ServiceState -ne "Stopped") + Write-Verbose 'Checking [TcpPort property].' + if ($desiredState.TcpPort -ine $getTargetResourceResult.TcpPort) + { + $fromTcpPort = $getTargetResourceResult.TcpPort + if ($fromTcpPort -eq '') { - Start-Sleep -Milliseconds 500 - $sqlService.Refresh() + $fromTcpPort = 'none' } - Write-Verbose "[$dbServiceName] service stopped" - - Write-Verbose "Starting [$dbServiceName] service ..." - $sqlService.Start() - while($sqlService.ServiceState -ne "Running") + $toTcpPort = $desiredState.TcpPort + if ($toTcpPort -eq '') { - Start-Sleep -Milliseconds 500 - $sqlService.Refresh() + $toTcpPort = 'none' } - Write-Verbose "[$dbServiceName] service started" - if ($startAgent) - { - Write-Verbose "Staring [$agtServiceName] service ..." - $agentService.Start() - while($agentService.ServiceState -ne "Running") - { - Start-Sleep -Milliseconds 500 - $agentService.Refresh() - } - Write-Verbose "[$agtServiceName] service started" - } + Write-Verbose "Updating [TcpPort] from $($fromTcpPort) to $($toTcpPort)." + $tcp.IPAddresses['IPAll'].IPAddressProperties['TcpPort'].Value = $desiredState.TcpPort + $tcp.Alter() + + $isRestartNeeded = $true + } + + if ($RestartService -and $isRestartNeeded) + { + Restart-SqlService -SQLServer $SQLServer -SQLInstanceName $InstanceName -Timeout $RestartTimeout } } - Finally + finally { - [System.AppDomain]::Unload($dom_set) + Unregister-SqlAssemblies -ApplicationDomain $applicationDomainObject } } +<# + .SYNOPSIS + Sets the SQL Server network properties. + + .PARAMETER SQLServer + The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. + + Not used in Test-TargetResource. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER ProtocolName + The name of network protocol to be configured. Only tcp is currently supported. + + .PARAMETER IsEnabled + Enables or disables the network protocol. + + .PARAMETER TcpDynamicPorts + Set the value to '0' if dynamic ports should be used. + If static port should be used set this to a empty string value. + Value can not be set to '0' if TcpPort is also set to a value. + + .PARAMETER TcpPort + The TCP port(s) that SQL Server should be listening on. + If the IP address should listen on more than one port, list all ports + separated with a comma ('1433,1500,1501'). To use this parameter set + TcpDynamicPorts to the value '' (empty string). + + .PARAMETER RestartService + If set to $true then SQL Server and dependent services will be restarted + if a change to the configuration is made. The default value is $false. + + Not used in Test-TargetResource. + + .PARAMETER RestartTimeout + Timeout value for restarting the SQL Server services. The default value + is 120 seconds. + + Not used in Test-TargetResource. +#> Function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param( - [parameter(Mandatory = $true)] + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $SQLServer = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] [System.String] $InstanceName, - [parameter(Mandatory = $true)] - [ValidateSet("tcp")] + [Parameter(Mandatory = $true)] + [ValidateSet('Tcp')] [System.String] $ProtocolName, + [Parameter()] [System.Boolean] $IsEnabled, - [ValidateSet("0")] + [Parameter()] + [ValidateSet('0','')] [System.String] - $TCPDynamicPorts, + $TcpDynamicPorts, + [Parameter()] [System.String] - $TCPPort, + $TcpPort, + [Parameter()] [System.Boolean] - $RestartService = $false + $RestartService = $false, + + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 ) - Write-Verbose "xSQLServerNetwork.Test-TargetResource ..." - Write-Verbose "Parameters: InstanceName = $InstanceName; ProtocolName = $ProtocolName; IsEnabled=$IsEnabled; TCPDynamicPorts = $TCPDynamicPorts; TCPPort = $TCPPort; RestartService=$RestartService;" - - $desiredState = @{ - InstanceName = $InstanceName - ProtocolName = $ProtocolName - IsEnabled = $IsEnabled - TCPDynamicPorts = $TCPDynamicPorts - TCPPort = $TCPPort - } - - Write-Verbose "Calling xSQLServerNetwork.Get-TargetResource ..." - $currentState = Get-TargetResource -InstanceName $InstanceName -ProtocolName $ProtocolName - - Write-Verbose "Comparing desiredState with currentSate ..." - foreach($key in $desiredState.Keys) + if ($TcpDynamicPorts -eq '0' -and $TcpPort) + { + throw New-TerminatingError -ErrorType UnableToUseBothDynamicAndStaticPort -ErrorCategory InvalidOperation + } + + $getTargetResourceResult = Get-TargetResource -InstanceName $InstanceName -ProtocolName $ProtocolName + + Write-Verbose "Comparing desired state with current state." + + $isInDesiredState = $true + + if ($ProtocolName -ne $getTargetResourceResult.ProtocolName) + { + Write-Verbose ('Expected protocol to be ''{0}'', but was ''{1}''.' -f $ProtocolName, $getTargetResourceResult.ProtocolName) + + $isInDesiredState = $false + } + + if ($PSBoundParameters.ContainsKey('IsEnabled')) { - if($currentState.Keys -eq $key) + if ($IsEnabled -ne $getTargetResourceResult.IsEnabled) { - if($desiredState[$key] -ine $currentState[$key] ) - { - Write-Verbose "$key is different: desired = $($desiredState[$key]); current = $($currentState[$key])" - return $false + $evaluateEnableOrDisable = @{ + $true='enabled' + $false='disabled' } + + Write-Verbose ('Expected protocol to be {0}, but was {1}.' -f $evaluateEnableOrDisable[$IsEnabled], $evaluateEnableOrDisable[$getTargetResourceResult.IsEnabled]) + + $isInDesiredState = $false } - else + } + + if ($PSBoundParameters.ContainsKey('TcpDynamicPorts')) + { + if ($TcpDynamicPorts -eq '0' -and $getTargetResourceResult.TcpDynamicPorts -eq '') { - Write-Verbose "$key is missing" - return $false + Write-Verbose 'Expected tcp dynamic port to be used, but it was not.' + + $isInDesiredState = $false } } - Write-Verbose "States match" - return $true -} + if ($PSBoundParameters.ContainsKey('TcpPort')) + { + if ($getTargetResourceResult.TcpPort -eq '') + { + Write-Verbose 'Expected tcp static port to be used, but it was not.' -Function GetVersion -{ - param( - [parameter(Mandatory = $true)] - [System.String] - $InstanceName - ) + $isInDesiredState = $false + } + elseif ($TcpPort -ne $getTargetResourceResult.TcpPort) + { + Write-Verbose ('Expected tcp static port to be ''{0}'', but was ''{1}''.' -f $TcpPort, $getTargetResourceResult.TcpPort) + + $isInDesiredState = $false + } + } + + if ($isInDesiredState) + { + Write-Verbose "In desired state." + } - $instanceId = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL").$InstanceName - $sqlVersion = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$instanceId\Setup").Version - $sqlVersion.Split(".")[0] + return $isInDesiredState } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.schema.mof b/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.schema.mof index cf1be7c24..fe3a852c8 100644 --- a/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.schema.mof +++ b/DSCResources/MSFT_xSQLServerNetwork/MSFT_xSQLServerNetwork.schema.mof @@ -1,10 +1,12 @@ [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerNetwork")] class MSFT_xSQLServerNetwork : OMI_BaseResource { - [Key, Description("SQL Server instance name of which network protocol should be configured")] String InstanceName; - [Required, Description("Network protocol name that should be configured"), ValueMap{"tcp"}, Values{"tcp"}] String ProtocolName; - [Write, Description("Is network protocol should be enabled or disabled")] Boolean IsEnabled; - [Write, Description("If dynamic ports are used should be set to 0, otherwise leave empty"), ValueMap{"0"}, Values{"0"}] String TCPDynamicPorts; - [Write, Description("Sets static port for TCP/IP")] String TCPPort; - [Write, Description("Controls if affected SQL Service should be restarted automatically")] Boolean RestartService; + [Key, Description("The name of the SQL instance to be configured.")] String InstanceName; + [Required, Description("The name of network protocol to be configured. Only tcp is currently supported."), ValueMap{"Tcp"}, Values{"Tcp"}] String ProtocolName; + [Write, Description("The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME.")] String SQLServer; + [Write, Description("Enables or disables the network protocol.")] Boolean IsEnabled; + [Write, Description("Set the value to '0' if dynamic ports should be used. If static port should be used set this to a empty string value. Value can not be set to '0' if TcpPort is also set to a value."), ValueMap{"0",""}, Values{"0",""}] String TcpDynamicPorts; + [Write, Description("The TCP port(s) that SQL Server should be listening on. If the IP address should listen on more than one port, list all ports separated with a comma ('1433,1500,1501'). To use this parameter set TcpDynamicPorts to the value '' (empty string).")] String TcpPort; + [Write, Description("If set to $true then SQL Server and dependent services will be restarted if a change to the configuration is made. The default value is $false.")] Boolean RestartService; + [Write, Description("Timeout value for restarting the SQL Server services. The default value is 120 seconds.")] UInt16 RestartTimeout; }; diff --git a/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.psm1 b/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.psm1 index 5a75b2f30..f51224bdb 100644 --- a/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.psm1 +++ b/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.psm1 @@ -1,8 +1,22 @@ -$ErrorActionPreference = "Stop" +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force +<# + .SYNOPSIS + Returns the current state of the permissions for the principal (login). -$script:currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Import-Module $script:currentPath\..\..\xSQLServerHelper.psm1 -ErrorAction Stop + .PARAMETER InstanceName + The name of the SQL instance to be configured. + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Principal + The login to which permission will be set. + + .PARAMETER Permission + The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. +#> function Get-TargetResource { [CmdletBinding()] @@ -11,120 +25,177 @@ function Get-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [System.String] - $NodeName, + $NodeName = $env:COMPUTERNAME, [Parameter(Mandatory = $true)] [System.String] $Principal, - [ValidateSet('AlterAnyAvailabilityGroup','ViewServerState','AlterAnyEndPoint')] + [Parameter()] + [ValidateSet('ConnectSql','AlterAnyAvailabilityGroup','ViewServerState','AlterAnyEndPoint')] [System.String[]] $Permission ) New-VerboseMessage -Message "Enumerating permissions for $Principal" - try { - $instance = Get-SQLPSInstance -NodeName $NodeName -InstanceName $InstanceName - - $permissionSet = Get-SQLServerPermissionSet -Permission $Permission - $enumeratedPermission = $instance.EnumServerPermissions( $Principal, $permissionSet ) | Where-Object { $_.PermissionState -eq "Grant" } - if( $null -ne $enumeratedPermission) { - $grantedPermissionSet = Get-SQLServerPermissionSet -PermissionSet $enumeratedPermission.PermissionType - if( -not ( Compare-Object -ReferenceObject $permissionSet -DifferenceObject $grantedPermissionSet -Property $Permission ) ) { - $ensure = "Present" - } else { - $ensure = "Absent" + try + { + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + # Gets a set of permissions granted based on the desired permissions in $Permission + $desiredPermissionSet = Get-SQLServerPermissionSet -Permission $Permission + $grantedPermissionSet = $sqlServerObject.EnumServerPermissions( $Principal, $desiredPermissionSet ) | + Where-Object { $_.PermissionState -eq 'Grant' } + + if ($null -ne $grantedPermissionSet) + { + $concatenatedGrantedPermissionSet = Get-SQLServerPermissionSet -PermissionSet $grantedPermissionSet.PermissionType + + # Compare desired and granted permissions based on the permissions properties from $Permission. + if (-not (Compare-Object -ReferenceObject $desiredPermissionSet -DifferenceObject $concatenatedGrantedPermissionSet -Property $Permission)) + { + $ensure = 'Present' + } + else + { + $ensure = 'Absent' } - $grantedPermission = Get-SQLPermission -ServerPermissionSet $grantedPermissionSet - } else { - $ensure = "Absent" - $grantedPermission = "" + # Return granted permissions as a string array. + $grantedPermission = Get-SQLPermission -ServerPermissionSet $concatenatedGrantedPermissionSet + } + else + { + $ensure = 'Absent' + $grantedPermission = '' } - } catch { + } + catch + { throw New-TerminatingError -ErrorType PermissionGetError -FormatArgs @($Principal) -ErrorCategory InvalidOperation -InnerException $_.Exception } - $returnValue = @{ + return @{ InstanceName = [System.String] $InstanceName NodeName = [System.String] $NodeName Ensure = [System.String] $ensure Principal = [System.String] $Principal Permission = [System.String[]] $grantedPermission } - - return $returnValue } +<# + .SYNOPSIS + Grants or revokes the permission for the the principal (login). + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Ensure + If the permission should be present or absent. Default value is 'Present'. + + .PARAMETER Principal + The login to which permission will be set. + + .PARAMETER Permission + The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. +#> function Set-TargetResource { - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [System.String] - $NodeName, + $NodeName = $env:COMPUTERNAME, - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', [Parameter(Mandatory = $true)] [System.String] $Principal, - [ValidateSet('AlterAnyAvailabilityGroup','ViewServerState','AlterAnyEndPoint')] + [Parameter()] + [ValidateSet('ConnectSql','AlterAnyAvailabilityGroup','ViewServerState','AlterAnyEndPoint')] [System.String[]] $Permission ) - $parameters = @{ + $getTargetResourceParameters = @{ InstanceName = [System.String] $InstanceName NodeName = [System.String] $NodeName Principal = [System.String] $Principal Permission = [System.String[]] $Permission } - - $permissionState = Get-TargetResource @parameters - if( $null -ne $permissionState ) { - if( $Ensure -ne "" ) { - if( $permissionState.Ensure -ne $Ensure ) { - $instance = Get-SQLPSInstance -NodeName $NodeName -InstanceName $InstanceName - if( $null -ne $instance ) { - $permissionSet = Get-SQLServerPermissionSet -Permission $Permission - - if( $Ensure -eq "Present") { - if( ( $PSCmdlet.ShouldProcess( $Principal, "Grant permission" ) ) ) { - $instance.Grant($permissionSet, $Principal ) - } - } else { - if( ( $PSCmdlet.ShouldProcess( $Principal, "Revoke permission" ) ) ) { - $instance.Revoke($permissionSet, $Principal ) - } - } - } else { - throw New-TerminatingError -ErrorType PrincipalNotFound -FormatArgs @($Principal) -ErrorCategory ObjectNotFound - } - } else { - New-VerboseMessage -Message "State is already $Ensure" + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + if ($getTargetResourceResult.Ensure -ne $Ensure) + { + try + { + $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName + + $permissionSet = Get-SQLServerPermissionSet -Permission $Permission + + if ($Ensure -eq 'Present') + { + Write-Verbose -Message ('Grant permission for ''{0}''' -f $Principal) + + $sqlServerObject.Grant($permissionSet, $Principal) + } + else + { + Write-Verbose -Message ('Revoke permission for ''{0}''' -f $Principal) + + $sqlServerObject.Revoke($permissionSet, $Principal) } - } else { - throw New-TerminatingError -ErrorType PermissionMissingEnsure -FormatArgs @($Principal) -ErrorCategory InvalidOperation - } - } else { - throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult + } + catch + { + throw New-TerminatingError -ErrorType ChangingPermissionFailed -FormatArgs @($Principal) -ErrorCategory InvalidOperation -InnerException $_.Exception + } + } + else + { + New-VerboseMessage -Message "State is already $Ensure" } } +<# + .SYNOPSIS + Tests if the principal (login) has the desired permissions. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER NodeName + The host name of the SQL Server to be configured. + + .PARAMETER Ensure + If the permission should be present or absent. Default value is 'Present'. + + .PARAMETER Principal + The login to which permission will be set. + + .PARAMETER Permission + The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. +#> function Test-TargetResource { [CmdletBinding()] @@ -133,114 +204,148 @@ function Test-TargetResource ( [Parameter(Mandatory = $true)] [System.String] - $InstanceName = "DEFAULT", + $InstanceName, - [Parameter(Mandatory = $true)] + [Parameter()] [System.String] - $NodeName, + $NodeName = $env:COMPUTERNAME, - [ValidateSet("Present","Absent")] + [Parameter()] + [ValidateSet('Present','Absent')] [System.String] - $Ensure, + $Ensure = 'Present', [Parameter(Mandatory = $true)] [System.String] $Principal, - [ValidateSet('AlterAnyAvailabilityGroup','ViewServerState','AlterAnyEndPoint')] + [Parameter()] + [ValidateSet('ConnectSql','AlterAnyAvailabilityGroup','ViewServerState','AlterAnyEndPoint')] [System.String[]] $Permission ) - $parameters = @{ - InstanceName = [System.String] $InstanceName - NodeName = [System.String] $NodeName - Principal = [System.String] $Principal - Permission = [System.String[]] $Permission - } - - New-VerboseMessage -Message "Testing state of permissions for $Principal" - - $permissionState = Get-TargetResource @parameters - if( $null -ne $permissionState ) { - [System.Boolean] $result = $false - if( $permissionState.Ensure -eq $Ensure) { - $result = $true - } - } else { - throw New-TerminatingError -ErrorType UnexpectedErrorFromGet -ErrorCategory InvalidResult + $getTargetResourceParameters = @{ + InstanceName = $InstanceName + NodeName = $NodeName + Principal = $Principal + Permission = $Permission } - return $result + New-VerboseMessage -Message "Verifying permissions for $Principal" + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + return $getTargetResourceResult.Ensure -eq $Ensure } +<# + .SYNOPSIS + Takes a Microsoft.SqlServer.Management.Smo.ServerPermissionSet object which will be + enumerated and returned as a string array. + + .PARAMETER ServerPermissionSet + A PermissionSet object which should be enumerated. +#> function Get-SQLPermission { [CmdletBinding()] - [OutputType([String[]])] - param ( - [Parameter(Mandatory,ParameterSetName="ServerPermissionSet",HelpMessage="Takes a PermissionSet which will be enumerated to return a string array.")] + [OutputType([System.String[]])] + param + ( + [Parameter(Mandatory = $true)] [Microsoft.SqlServer.Management.Smo.ServerPermissionSet] [ValidateNotNullOrEmpty()] $ServerPermissionSet ) - [String[]] $permission = @() - - if( $ServerPermissionSet ) { - foreach( $Property in $($ServerPermissionSet | Get-Member -Type Property) ) { - if( $ServerPermissionSet.$($Property.Name) ) { - $permission += $Property.Name - } + [System.String[]] $permissionArray = @() + + foreach ($property in $($ServerPermissionSet | Get-Member -Type Property)) + { + if ($ServerPermissionSet.$($property.Name)) + { + $permissionArray += $property.Name } } - - return [String[]] $permission + + return $permissionArray } +<# + .SYNOPSIS + Takes either an array of strings or an array of Microsoft.SqlServer.Management.Smo.ServerPermissionSet objects which + will be enumerated and concatenated to a single Microsoft.SqlServer.Management.Smo.ServerPermissionSet object. + + .PARAMETER Permission + An array of strings which should be concatenated to a single Microsoft.SqlServer.Management.Smo.ServerPermissionSet object. + + .PARAMETER ServerPermissionSet + An array of Microsoft.SqlServer.Management.Smo.ServerPermissionSet objects which should be concatenated to a single + Microsoft.SqlServer.Management.Smo.ServerPermissionSet object. +#> function Get-SQLServerPermissionSet { [CmdletBinding()] - [OutputType([Object])] + [OutputType([Object])] param ( - [Parameter(Mandatory,ParameterSetName="Permission",HelpMessage="Takes an array of strings which will be concatenated to a single ServerPermissionSet.")] + [Parameter(Mandatory = $true,ParameterSetName='Permission')] [System.String[]] [ValidateNotNullOrEmpty()] $Permission, - - [Parameter(Mandatory,ParameterSetName="ServerPermissionSet",HelpMessage="Takes an array of ServerPermissionSet which will be concatenated to a single ServerPermissionSet.")] + + [Parameter(Mandatory = $true,ParameterSetName='ServerPermissionSet')] [Microsoft.SqlServer.Management.Smo.ServerPermissionSet[]] [ValidateNotNullOrEmpty()] $PermissionSet ) - if( $Permission ) { - [Microsoft.SqlServer.Management.Smo.ServerPermissionSet] $permissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerPermissionSet + if ($Permission) + { + [Microsoft.SqlServer.Management.Smo.ServerPermissionSet] $concatenatedPermissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerPermissionSet - foreach( $currentPermission in $Permission ) { - $permissionSet.$($currentPermission) = $true + foreach ($currentPermission in $Permission) + { + $concatenatedPermissionSet.$($currentPermission) = $true } - } else { - $permissionSet = Merge-SQLPermissionSet -Object $PermissionSet } - - return $permissionSet + else + { + $concatenatedPermissionSet = Merge-SQLPermissionSet -Object $PermissionSet + } + + return $concatenatedPermissionSet } -function Merge-SQLPermissionSet { - param ( - [Parameter(Mandatory)] - [Microsoft.SqlServer.Management.Smo.ServerPermissionSet[]] +<# + .SYNOPSIS + Merges an array of any PermissionSet objects into a single PermissionSet. + + The though with this helper function si it can be used for any permission set object + because all inheriths from Microsoft.SqlServer.Management.Smo.PermissionSetBase. + + .PARAMETER Object + An array of strings which should be concatenated to a single PermissionSet object. +#> +function Merge-SQLPermissionSet +{ + param + ( + [Parameter(Mandatory = $true)] + [Object[]] [ValidateNotNullOrEmpty()] $Object ) - + $baseObject = New-Object -TypeName ($Object[0].GetType()) - foreach ( $currentObject in $Object ) { - foreach( $Property in $($currentObject | Get-Member -Type Property) ) { - if( $currentObject.$($Property.Name) ) { + foreach ($currentObject in $Object) + { + foreach ($Property in $($currentObject | Get-Member -Type Property)) + { + if ($currentObject.$($Property.Name)) + { $baseObject.$($Property.Name) = $currentObject.$($Property.Name) } } diff --git a/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.schema.mof b/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.schema.mof index d92f251aa..d67969841 100644 --- a/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.schema.mof +++ b/DSCResources/MSFT_xSQLServerPermission/MSFT_xSQLServerPermission.schema.mof @@ -1,11 +1,9 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerPermission")] class MSFT_xSQLServerPermission : OMI_BaseResource { - [Key, Description("The SQL Server instance name.")] String InstanceName; - [Required, Description("The host name or FQDN.")] String NodeName; - [Write, Description("If the permission should be present or absent."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Required, Description("The login to which permission will be set.")] String Principal; - [Write, Description("The permission to set for the login."), ValueMap{"AlterAnyAvailabilityGroup","ViewServerState","AlterAnyEndPoint"}, Values{"AlterAnyAvailabilityGroup","ViewServerState","AlterAnyEndPoint"}] String Permission[]; + [Key, Description("The name of the SQL instance to be configured.")] String InstanceName; + [Write, Description("The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME.")] String NodeName; + [Write, Description("If the permission should be present or absent. Default value is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Key, Description("The login to which permission will be set.")] String Principal; + [Write, Description("The permission to set for the login. Valid values are ConnectSql, AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint."), ValueMap{"ConnectSql","AlterAnyAvailabilityGroup","ViewServerState","AlterAnyEndPoint"}, Values{"ConnectSql","AlterAnyAvailabilityGroup","ViewServerState","AlterAnyEndPoint"}] String Permission[]; }; - diff --git a/DSCResources/MSFT_xSQLServerRSConfig/MSFT_xSQLServerRSConfig.psm1 b/DSCResources/MSFT_xSQLServerRSConfig/MSFT_xSQLServerRSConfig.psm1 index 6265f171c..f619d2b3f 100644 --- a/DSCResources/MSFT_xSQLServerRSConfig/MSFT_xSQLServerRSConfig.psm1 +++ b/DSCResources/MSFT_xSQLServerRSConfig/MSFT_xSQLServerRSConfig.psm1 @@ -89,14 +89,17 @@ function Set-TargetResource if(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS" -Name $InstanceName -ErrorAction SilentlyContinue) { - Invoke-Command -ComputerName . -Credential $SQLAdminCredential -Authentication Credssp -ScriptBlock { + Invoke-Command -ComputerName . -Credential $SQLAdminCredential -ScriptBlock { + # this is a separate PS session, need to load Common Code again + Import-Module $using:currentPath\..\..\xSQLServerHelper.psm1 -Verbose -ErrorAction Stop + # smart import of the SQL module + Import-SQLPSModule + $InstanceName = $args[0] $RSSQLServer = $args[1] $RSSQLInstanceName = $args[2] $InstanceKey = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS" -Name $InstanceName).$InstanceName $SQLVersion = ((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$InstanceKey\Setup" -Name "Version").Version).Split(".")[0] - $DBCreateFile = [IO.Path]::GetTempFileName() - $DBRightsFile = [IO.Path]::GetTempFileName() if($InstanceName -eq "MSSQLSERVER") { $RSServiceName = "ReportServer" @@ -123,70 +126,25 @@ function Set-TargetResource $RSConfig = Get-WmiObject -Class MSReportServer_ConfigurationSetting -Namespace "root\Microsoft\SQLServer\ReportServer\RS_$InstanceName\v$SQLVersion\Admin" if($RSConfig.VirtualDirectoryReportServer -ne $RSVirtualDirectory) { - $RSConfig.SetVirtualDirectory("ReportServerWebService",$RSVirtualDirectory,$Language) - $RSConfig.ReserveURL("ReportServerWebService","http://+:80",$Language) + $null = $RSConfig.SetVirtualDirectory("ReportServerWebService",$RSVirtualDirectory,$Language) + $null = $RSConfig.ReserveURL("ReportServerWebService","http://+:80",$Language) } if($RSConfig.VirtualDirectoryReportManager -ne $RMVirtualDirectory) { - $RSConfig.SetVirtualDirectory("ReportManager",$RMVirtualDirectory,$Language) - $RSConfig.ReserveURL("ReportManager","http://+:80",$Language) + $null = $RSConfig.SetVirtualDirectory("ReportManager",$RMVirtualDirectory,$Language) + $null = $RSConfig.ReserveURL("ReportManager","http://+:80",$Language) } - $RSScript = $RSConfig.GenerateDatabaseCreationScript($RSDatabase,$Language,$false) - $RSScript.Script | Out-File $DBCreateFile + $RSCreateScript = $RSConfig.GenerateDatabaseCreationScript($RSDatabase,$Language,$false) # Determine RS service account $RSSvcAccountUsername = (Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $RSServiceName}).StartName - if(($RSSvcAccountUsername -eq "LocalSystem") -or (($RSSvcAccountUsername.Length -ge 10) -and ($RSSvcAccountUsername.SubString(0,10) -eq "NT Service"))) - { - $RSSvcAccountUsername = $RSConfig.MachineAccountIdentity - } - $RSScript = $RSConfig.GenerateDatabaseRightsScript($RSSvcAccountUsername,$RSDatabase,$false,$true) - $RSScript.Script | Out-File $DBRightsFile - - # Get path to sqlcmd.exe - $SQLCmdLocations = @( - @{ - Key = "4B5EB208A08862C4C9A0A2924D2613FF" - Name = "BAF8FF4572ED7814281FBEEAA6EE68A9" - } - @{ - Key = "4B5EB208A08862C4C9A0A2924D2613FF" - Name = "2BE7307A359F21B48B3491F5D489D81A" - } - @{ - Key = "4B5EB208A08862C4C9A0A2924D2613FF" - Name = "17E375D97701E7C44BBDE4225A2D4BB8" - } - @{ - Key = "A4A2A5C7B23E40145A6AFA7667643E85" - Name = "8B035CCA4B6B6D045BB9514286FC740D" - } - ) - $SQLCmdPath = "" - foreach($SQLCmdLocation in $SQLCmdLocations) - { - if($SQLCmdPath -eq "") - { - if(Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\" + $SQLCmdLocation.Key) -Name $SQLCmdLocation.Name -ErrorAction SilentlyContinue) - { - - if(Test-Path -Path (Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\" + $SQLCmdLocation.Key) -Name $SQLCmdLocation.Name).($SQLCmdLocation.Name)) - { - $SQLCmdPath = (Get-ItemProperty -Path ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\" + $SQLCmdLocation.Key) -Name $SQLCmdLocation.Name).($SQLCmdLocation.Name) - } - } - } - } - if($SQLCmdPath -ne "") - { - & "$SQLCmdPath" -S $RSConnection -i $DBCreateFile - & "$SQLCmdPath" -S $RSConnection -i $DBRightsFile - $RSConfig.SetDatabaseConnection($RSConnection,$RSDatabase,2,"","") - $RSConfig.InitializeReportServer($RSConfig.InstallationID) - } + $RSRightsScript = $RSConfig.GenerateDatabaseRightsScript($RSSvcAccountUsername,$RSDatabase,$false,$true) + + Invoke-Sqlcmd -ServerInstance $RSConnection -Query $RSCreateScript.Script + Invoke-Sqlcmd -ServerInstance $RSConnection -Query $RSRightsScript.Script + $RSConfig.SetDatabaseConnection($RSConnection,$RSDatabase,2,"","") + $RSConfig.InitializeReportServer($RSConfig.InstallationID) - Remove-Item -Path $DBCreateFile - Remove-Item -Path $DBRightsFile } -ArgumentList @($InstanceName,$RSSQLServer,$RSSQLInstanceName) } diff --git a/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.psm1 b/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.psm1 index 5a2529b6e..f618c6590 100644 --- a/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.psm1 +++ b/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.psm1 @@ -1,146 +1,541 @@ -Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -ChildPath 'xSQLServerHelper.psm1') -Force +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force +<# + .SYNOPSIS + This function gets the sql server role properties. + + .PARAMETER Members + The members the server role should have. + + .PARAMETER MembersToInclude + The members the server role should include. + .PARAMETER MembersToExclude + The members the server role should exclude. + + .PARAMETER ServerRoleName + The name of server role to be created or dropped. + + .PARAMETER SQLServer + The host name of the SQL Server to be configured. + + .PARAMETER SQLInstanceName + The name of the SQL instance to be configured. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, + [Parameter()] + [System.String[]] + $Members, - [ValidateSet('Present','Absent')] - [System.String] - $Ensure = 'Present', + [Parameter()] + [System.String[]] + $MembersToInclude, - [Parameter(Mandatory = $true)] - [ValidateSet('bulkadmin','dbcreator','diskadmin','processadmin','public','securityadmin','serveradmin','setupadmin','sysadmin')] + [Parameter()] [System.String[]] - $ServerRole, + $MembersToExclude, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerRoleName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName ) - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + $ensure = 'Present' - if ($sql) + if ($sqlServerObject) { - Write-Verbose "Getting SQL Server roles for $Name on SQL Server $SQLServer." - $confirmSqlServerRole = Confirm-SqlServerRoleMember -SQL $sql -LoginName $Name -ServerRole $ServerRole - if ($confirmSqlServerRole) + Write-Verbose -Message "Getting properties of SQL Server role '$ServerRoleName'." + if ($sqlServerRoleObject = $sqlServerObject.Roles[$ServerRoleName]) { - $Ensure = 'Present' + try + { + $membersInRole = $sqlServerRoleObject.EnumMemberNames() + } + catch + { + throw New-TerminatingError -ErrorType EnumMemberNamesServerRoleGetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$ServerRoleName) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } + + if ($Members) + { + if ($MembersToInclude -or $MembersToExclude) + { + throw New-TerminatingError -ErrorType MembersToIncludeAndExcludeParamMustBeNull ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidArgument + } + + if ( $null -ne (Compare-Object -ReferenceObject $membersInRole -DifferenceObject $Members)) + { + New-VerboseMessage -Message "The desired members are not present in server role $ServerRoleName" + $ensure = 'Absent' + } + } + else + { + if ($MembersToInclude) + { + foreach ($memberToInclude in $MembersToInclude) + { + if ( -not ($membersInRole.Contains($memberToInclude))) + { + New-VerboseMessage -Message "The included members are not present in server role $ServerRoleName" + $ensure = 'Absent' + } + } + } + + if ($MembersToExclude) + { + foreach ($memberToExclude in $MembersToExclude) + { + if ($membersInRole.Contains($memberToExclude)) + { + New-VerboseMessage -Message "The excluded members are present in server role $ServerRoleName" + $ensure = 'Absent' + } + } + } + } } else { - $Ensure = 'Absent' + $ensure = 'Absent' } } - else - { - $Ensure = 'Absent' - } $returnValue = @{ - Ensure = $Ensure - Name = $Name - ServerRole = $ServerRole - SQLServer = $SQLServer - SQLInstanceName = $SQLInstanceName + Ensure = $ensure + Members = $membersInRole + MembersToInclude = $MembersToInclude + MembersToExclude = $MembersToExclude + ServerRoleName = $ServerRoleName + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName } $returnValue } +<# + .SYNOPSIS + This function sets the sql server role properties. + + .PARAMETER Ensure + When set to 'Present', the server role will be created. + When set to 'Absent', the server role will be dropped. + + .PARAMETER Members + The members the server role should have. + + .PARAMETER MembersToInclude + The members the server role should include. + + .PARAMETER MembersToExclude + The members the server role should exclude. + + .PARAMETER ServerRoleName + The name of server role to be created or dropped. + + .PARAMETER SQLServer + The host name of the SQL Server to be configured. + + .PARAMETER SQLInstanceName + The name of the SQL instance to be configured. +#> function Set-TargetResource { [CmdletBinding()] param ( + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.String[]] + $Members, + + [Parameter()] + [System.String[]] + $MembersToInclude, + + [Parameter()] + [System.String[]] + $MembersToExclude, + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] - $Name, + $ServerRoleName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $SQLServer, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $SQLInstanceName + ) + + $sqlServerObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + + if ($sqlServerObject) + { + Write-Verbose -Message "Setting properties of SQL Server role '$ServerRoleName'." + + switch ($Ensure) + { + 'Absent' + { + try + { + $sqlServerRoleObjectToDrop = $sqlServerObject.Roles[$ServerRoleName] + if ($sqlServerRoleObjectToDrop) + { + Write-Verbose -Message "Trying to drop the SQL Server role '$ServerRoleName'." + $sqlServerRoleObjectToDrop.Drop() + New-VerboseMessage -Message "Dropped the SQL Server role '$ServerRoleName'." + } + } + catch + { + throw New-TerminatingError -ErrorType DropServerRoleSetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$ServerRoleName) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } + } + + 'Present' + { + if ($null -eq $sqlServerObject.Roles[$ServerRoleName]) + { + try + { + $sqlServerRoleObjectToCreate = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerRole ` + -ArgumentList $sqlServerObject,$ServerRoleName + if ($sqlServerRoleObjectToCreate) + { + Write-Verbose -Message "Creating the SQL Server role '$ServerRoleName'." + $sqlServerRoleObjectToCreate.Create() + New-VerboseMessage -Message "Created the SQL Server role '$ServerRoleName'." + } + } + catch + { + throw New-TerminatingError -ErrorType CreateServerRoleSetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$ServerRoleName) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } + } + + if ($Members) + { + if ($MembersToInclude -or $MembersToExclude) + { + throw New-TerminatingError -ErrorType MembersToIncludeAndExcludeParamMustBeNull ` + -FormatArgs @($SQLServer,$SQLInstanceName) ` + -ErrorCategory InvalidArgument + } + + $memberNamesInRoleObject = $sqlServerObject.Roles[$ServerRoleName].EnumMemberNames() + + foreach ($memberName in $memberNamesInRoleObject) + { + if ( -not ($Members.Contains($memberName))) + { + Remove-SqlDscServerRoleMember -SqlServerObject $sqlServerObject ` + -LoginName $memberName ` + -ServerRoleName $ServerRoleName + } + } + + foreach ($memberToAdd in $Members) + { + if ( -not ($memberNamesInRoleObject.Contains($memberToAdd))) + { + Add-SqlDscServerRoleMember -SqlServerObject $sqlServerObject ` + -LoginName $memberToAdd ` + -ServerRoleName $ServerRoleName + } + } + } + else + { + if ($MembersToInclude) + { + $memberNamesInRoleObject = $sqlServerObject.Roles[$ServerRoleName].EnumMemberNames() + + foreach ($memberToInclude in $MembersToInclude) + { + if ( -not ($memberNamesInRoleObject.Contains($memberToInclude))) + { + Add-SqlDscServerRoleMember -SqlServerObject $sqlServerObject ` + -LoginName $memberToInclude ` + -ServerRoleName $ServerRoleName + } + } + } + + if ($MembersToExclude) + { + $memberNamesInRoleObject = $sqlServerObject.Roles[$ServerRoleName].EnumMemberNames() + + foreach ($memberToExclude in $MembersToExclude) + { + if ($memberNamesInRoleObject.Contains($memberToExclude)) + { + Remove-SqlDscServerRoleMember -SqlServerObject $sqlServerObject ` + -LoginName $memberToExclude ` + -ServerRoleName $ServerRoleName + } + } + } + } + } + } + } +} + +<# + .SYNOPSIS + This function tests the sql server role properties. + + .PARAMETER Ensure + When set to 'Present', the server role will be created. + When set to 'Absent', the server role will be dropped. + .PARAMETER Members + The members the server role should have. + + .PARAMETER MembersToInclude + The members the server role should include. + + .PARAMETER MembersToExclude + The members the server role should exclude. + + .PARAMETER ServerRoleName + The name of server role to be created or dropped. + + .PARAMETER SQLServer + The host name of the SQL Server to be configured. + + .PARAMETER SQLInstanceName + The name of the SQL instance to be configured. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] [ValidateSet('Present','Absent')] [System.String] $Ensure = 'Present', - [Parameter(Mandatory = $true)] - [ValidateSet('bulkadmin','dbcreator','diskadmin','processadmin','public','securityadmin','serveradmin','setupadmin','sysadmin')] + [Parameter()] [System.String[]] - $ServerRole, + $Members, + + [Parameter()] + [System.String[]] + $MembersToInclude, + + [Parameter()] + [System.String[]] + $MembersToExclude, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerRoleName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] $SQLInstanceName ) - $sql = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + Write-Verbose -Message "Testing SQL Server role $ServerRoleName properties." + + $getTargetResourceParameters = @{ + SQLInstanceName = $PSBoundParameters.SQLInstanceName + SQLServer = $PSBoundParameters.SQLServer + ServerRoleName = $PSBoundParameters.ServerRoleName + Members = $PSBoundParameters.Members + MembersToInclude = $PSBoundParameters.MembersToInclude + MembersToExclude = $PSBoundParameters.MembersToExclude + } - if ($sql) + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $isServerRoleInDesiredState = $true + + switch ($Ensure) { - Write-Verbose "Setting SQL Server roles for $Name on SQL Server $SQLServer." - if ($Ensure -eq 'Present') + 'Absent' { - Add-SqlServerRoleMember -SQL $sql -LoginName $Name -ServerRole $ServerRole - New-VerboseMessage -Message "SQL Roles for $Name, successfullly added" + if ($getTargetResourceResult.Ensure -ne 'Absent') + { + New-VerboseMessage -Message "Ensure is set to Absent. The existing role $ServerRoleName should be dropped" + $isServerRoleInDesiredState = $false + } } - else + + 'Present' { - Remove-SqlServerRoleMember -SQL $sql -LoginName $Name -ServerRole $ServerRole - New-VerboseMessage -Message "SQL Roles for $Name, successfullly removed" + if ($getTargetResourceResult.Ensure -ne 'Present') + { + New-VerboseMessage -Message ("Ensure is set to Present. The missing role $ServerRoleName " + ` + "should be added or members are not correctly configured") + $isServerRoleInDesiredState = $false + } } } + + $isServerRoleInDesiredState } -function Test-TargetResource +<# + .SYNOPSIS + Add a user to a server role in the SQL Server instance provided. + + .PARAMETER SqlServerObject + 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 ServerRoleName + String containing the name of the server role which the user will be added as a member to. +#> +function Add-SqlDscServerRoleMember { [CmdletBinding()] - [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] - $Name, + $LoginName, - [ValidateSet('Present','Absent')] + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] - $Ensure = 'Present', + $ServerRoleName + ) + + if ( -not ($SqlServerObject.Logins[$LoginName]) ) + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($LoginName, $SQLServer, $SQLInstanceName) ` + -ErrorCategory ObjectNotFound + } + try + { + Write-Verbose -Message "Adding SQL login $LoginName in role $ServerRoleName" + $SqlServerObject.Roles[$ServerRoleName].AddMember($LoginName) + New-VerboseMessage -Message "SQL Role $ServerRoleName for $LoginName, successfullly added" + } + catch + { + throw New-TerminatingError -ErrorType AddMemberServerRoleSetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$ServerRoleName,$LoginName) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } +} + +<# + .SYNOPSIS + Remove a user in a server role in the SQL Server instance provided. + + .PARAMETER SqlServerObject + 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 ServerRoleName + String containing the name of the server role for which the user will be removed as a member. +#> +function Remove-SqlDscServerRoleMember +{ + [CmdletBinding()] + param + ( [Parameter(Mandatory = $true)] - [ValidateSet('bulkadmin','dbcreator','diskadmin','processadmin','public','securityadmin','serveradmin','setupadmin','sysadmin')] - [System.String[]] - $ServerRole, + [ValidateNotNullOrEmpty()] + [System.Object] + $SqlServerObject, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] - $SQLServer, + $LoginName, [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [System.String] - $SQLInstanceName + $ServerRoleName ) - Write-Verbose -Message "Testing SQL roles for login $Name" - - $currentValues = Get-TargetResource @PSBoundParameters - $PSBoundParameters.Ensure = $Ensure - return Test-SQLDscParameterState -CurrentValues $CurrentValues ` - -DesiredValues $PSBoundParameters ` - -ValuesToCheck @('Name', - 'ServerRole', - 'Ensure') + if ( -not ($SqlServerObject.Logins[$LoginName]) ) + { + throw New-TerminatingError -ErrorType LoginNotFound ` + -FormatArgs @($LoginName, $SQLServer, $SQLInstanceName) ` + -ErrorCategory ObjectNotFound + } + + try + { + Write-Verbose -Message "Removing SQL login $LoginName from role $ServerRoleName" + $SqlServerObject.Roles[$ServerRoleName].DropMember($LoginName) + New-VerboseMessage -Message "SQL Role $ServerRoleName for $LoginName, successfullly dropped" + } + catch + { + throw New-TerminatingError -ErrorType DropMemberServerRoleSetError ` + -FormatArgs @($SQLServer,$SQLInstanceName,$ServerRoleName,$LoginName) ` + -ErrorCategory InvalidOperation ` + -InnerException $_.Exception + } } Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.schema.mof b/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.schema.mof index 80490da4c..d7c1a397b 100644 --- a/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.schema.mof +++ b/DSCResources/MSFT_xSQLServerRole/MSFT_xSQLServerRole.schema.mof @@ -1,11 +1,11 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xSQLServerRole")] class MSFT_xSQLServerRole : OMI_BaseResource { - [Key, Description("The name of the SQL login. If LoginType='WindowsUser' or 'WindowsGroup', this is also the name of the user or group in format DOMAIN\name.")] String Name; - [Write, Description("Present to ensure that the login has the defined roles, or absent to ensure that these roles are not defined for the login. Default value is 'Present'"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Required, Description("Type of SQL role to add"), ValueMap{"bulkadmin","dbcreator","diskadmin","processadmin","public","securityadmin","serveradmin","setupadmin","sysadmin"}, Values{"bulkadmin","dbcreator","diskadmin","processadmin","public","securityadmin","serveradmin","setupadmin","sysadmin"}] String ServerRole[]; - [Required, Description("The SQL Server for the login.")] String SQLServer; - [Key, Description("The SQL instance for the login.")] String SQLInstanceName; + [Key, Description("The name of of SQL role to add or remove.")] String ServerRoleName; + [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; + [Write, Description("An enumerated value that describes if the server role is added (Present) or dropped (Absent). Default value is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("The members the server role should have. This parameter will replace all the current server role members with the specified members.")] String Members[]; + [Write, Description("The members the server role should include. This parameter will only add members to a server role. Can not be used at the same time as parameter Members.")] String MembersToInclude[]; + [Write, Description("The members the server role should exclude. This parameter will only remove members from a server role. Can only be used when parameter Ensure is set to 'Present'. Can not be used at the same time as parameter Members.")] String MembersToExclude[]; }; - diff --git a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof index 275ccd78c..855265b80 100644 --- a/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof +++ b/DSCResources/MSFT_xSQLServerScript/MSFT_xSQLServerScript.schema.mof @@ -4,8 +4,8 @@ class MSFT_xSQLServerScript : OMI_BaseResource [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; + [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[]; + [Write, Description("Specifies, as a string array, a scripting variable for use in the sql 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)")] 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 1b3d7fa54..fc7baed76 100644 --- a/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 +++ b/DSCResources/MSFT_xSQLServerSetup/MSFT_xSQLServerSetup.psm1 @@ -63,7 +63,7 @@ function Get-TargetResource $FailoverClusterNetworkName ) - if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster')) + if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster','Addnode')) { $sqlHostName = $FailoverClusterNetworkName } @@ -145,6 +145,45 @@ function Get-TargetResource New-VerboseMessage -Message 'Replication feature not detected' } + # Check if Data Quality Client sub component is configured + New-VerboseMessage -Message "Detecting Data Quality Client (HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\ConfigurationState)" + $isDQCInstalled = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\ConfigurationState").SQL_DQ_CLIENT_Full + if ($isDQCInstalled -eq 1) + { + New-VerboseMessage -Message 'Data Quality Client feature detected' + $features += 'DQC,' + } + else + { + New-VerboseMessage -Message 'Data Quality Client feature not detected' + } + + # Check if Data Quality Services sub component is configured + New-VerboseMessage -Message "Detecting Data Quality Services (HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\DQ\*)" + $isDQInstalled = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\DQ\*" -ErrorAction SilentlyContinue) + if ($isDQInstalled) + { + New-VerboseMessage -Message 'Data Quality Services feature detected' + $features += 'DQ,' + } + else + { + New-VerboseMessage -Message 'Data Quality Services feature not detected' + } + + # Check if Documentation Components "BOL" is configured + New-VerboseMessage -Message "Detecting Documentation Components (HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\ConfigurationState)" + $isBOLInstalled = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\ConfigurationState").SQL_BOL_Components + if ($isBOLInstalled -eq 1) + { + New-VerboseMessage -Message 'Documentation Components feature detected' + $features += 'BOL,' + } + else + { + New-VerboseMessage -Message 'Documentation Components feature not detected' + } + $clientComponentsFullRegistryPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($sqlVersion)0\Tools\Setup\Client_Components_Full" $registryClientComponentsFullFeatureList = (Get-ItemProperty -Path $clientComponentsFullRegistryPath -ErrorAction SilentlyContinue).FeatureList @@ -170,6 +209,17 @@ function Get-TargetResource New-VerboseMessage -Message 'Client Connectivity Tools Backwards Compatibility feature not detected' } + Write-Debug -Message "Detecting Client Tools SDK feature ($clientComponentsFullRegistryPath)" + if (($registryClientComponentsFullFeatureList -like '*SDK_Full=3*') -and ($registryClientComponentsFullFeatureList -like '*SDK_FNS=3*')) + { + New-VerboseMessage -Message 'Client Tools SDK feature detected' + $features += 'SDK,' + } + else + { + New-VerboseMessage -Message 'Client Tools SDK feature not detected' + } + $instanceId = $fullInstanceId.Split('.')[1] $instanceDirectory = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$fullInstanceId\Setup" -Name 'SqlProgramDir').SqlProgramDir.Trim("\") @@ -345,7 +395,7 @@ function Get-TargetResource $registryKeySharedWOWDir = 'C90BFAC020D87EA46811C836AD3C507F' } - '13' + { $_ -in ('13','14') } { $registryKeySharedDir = 'FEE2E540D20152D4597229B6CFBC0A69' $registryKeySharedWOWDir = 'A79497A344129F64CA7D69C56F5DD8B4' @@ -742,6 +792,9 @@ function Set-TargetResource $InstanceName = $InstanceName.ToUpper() $parametersToEvaluateTrailingSlash = @( + 'InstanceDir', + 'InstallSharedDir', + 'InstallSharedWOWDir', 'InstallSQLDataDir', 'SQLUserDBDir', 'SQLUserDBLogDir', @@ -761,10 +814,18 @@ function Set-TargetResource if ($PSBoundParameters.ContainsKey($parameterName)) { $parameterValue = Get-Variable -Name $parameterName -ValueOnly - if ($parameterValue) + + # Trim backslash, but only if the path contains a full path and not just a qualifier. + if ($parameterValue -and $parameterValue -notmatch '^[a-zA-Z]:\\$') { Set-Variable -Name $parameterName -Value $parameterValue.TrimEnd('\') } + + # If the path only contains a qualifier but no backslash ('M:'), then a backslash is added ('M:\'). + if ($parameterValue -match '^[a-zA-Z]:$') + { + Set-Variable -Name $parameterName -Value "$parameterValue\" + } } } @@ -810,12 +871,12 @@ function Set-TargetResource # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase $feature = $feature.ToUpper(); - if (($sqlVersion -eq '13') -and (($feature -eq 'SSMS') -or ($feature -eq 'ADV_SSMS'))) + if (($sqlVersion -in ('13','14')) -and ($feature -in ('ADV_SSMS','SSMS'))) { Throw New-TerminatingError -ErrorType FeatureNotSupported -FormatArgs @($feature) -ErrorCategory InvalidData } - if (!($getTargetResourceResult.Features.Contains($feature))) + if (-not ($getTargetResourceResult.Features.Contains($feature))) { $featuresToInstall += "$feature," } @@ -832,6 +893,7 @@ function Set-TargetResource { Set-Variable -Name 'InstallSharedDir' -Value '' } + if((Get-Variable -Name 'InstallSharedWOWDir' -ErrorAction SilentlyContinue) -and (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -ErrorAction SilentlyContinue)) { Set-Variable -Name 'InstallSharedWOWDir' -Value '' @@ -844,6 +906,7 @@ function Set-TargetResource { Set-Variable -Name 'InstallSharedDir' -Value '' } + if((Get-Variable -Name 'InstallSharedWOWDir' -ErrorAction SilentlyContinue) -and (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' -ErrorAction SilentlyContinue)) { Set-Variable -Name 'InstallSharedWOWDir' -Value '' @@ -856,18 +919,20 @@ function Set-TargetResource { Set-Variable -Name 'InstallSharedDir' -Value '' } + if((Get-Variable -Name 'InstallSharedWOWDir' -ErrorAction SilentlyContinue) -and (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -ErrorAction SilentlyContinue)) { Set-Variable -Name 'InstallSharedWOWDir' -Value '' } } - '13' + { $_ -in ('13','14') } { if((Get-Variable -Name 'InstallSharedDir' -ErrorAction SilentlyContinue) -and (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' -ErrorAction SilentlyContinue)) { Set-Variable -Name 'InstallSharedDir' -Value '' } + if((Get-Variable -Name 'InstallSharedWOWDir' -ErrorAction SilentlyContinue) -and (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' -ErrorAction SilentlyContinue)) { Set-Variable -Name 'InstallSharedWOWDir' -Value '' @@ -877,22 +942,23 @@ function Set-TargetResource $setupArguments = @{} - if ($Action -in @('PrepareFailoverCluster','CompleteFailoverCluster','InstallFailoverCluster')) + if ($Action -in @('PrepareFailoverCluster','CompleteFailoverCluster','InstallFailoverCluster','Addnode')) { - # Set the group name for this clustered instance. + # This was brought over from the old module. Should be removed (breaking change). $setupArguments += @{ - FailoverClusterGroup = $FailoverClusterGroupName - - # This was brought over from the old module. Should be removed (breaking change). SkipRules = 'Cluster_VerifyForErrors' } } - # Add the failover cluster network name if the action is either installing or completing a cluster + <# + Set the failover cluster group name and failover cluster network name for this clustered instance + if the action is either installing (InstallFailoverCluster) or completing (CompleteFailoverCluster) a cluster. + #> if ($Action -in @('CompleteFailoverCluster','InstallFailoverCluster')) { $setupArguments += @{ FailoverClusterNetworkName = $FailoverClusterNetworkName + FailoverClusterGroup = $FailoverClusterGroupName } } @@ -936,7 +1002,7 @@ function Set-TargetResource # 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 | ` - Add-Member -MemberType NoteProperty -Name 'IsPossibleOwner' -Value $false -PassThru + Add-Member -MemberType NoteProperty -Name 'IsPossibleOwner' -Value $false -PassThru # First map regular cluster volumes foreach ($diskResource in $availableStorage) @@ -1068,45 +1134,28 @@ function Set-TargetResource 'InstanceID', 'UpdateEnabled', 'UpdateSource', - 'Features', 'ProductKey', 'SQMReporting', - 'ErrorReporting', - 'InstallSharedDir', - 'InstallSharedWOWDir', - 'InstanceDir' + 'ErrorReporting' ) - if ($null -ne $BrowserSvcStartupType) + if ($Action -in @('Install','InstallFailoverCluster','PrepareFailoverCluster','CompleteFailoverCluster')) { - $argumentVars += 'BrowserSvcStartupType' + $argumentVars += @( + 'Features', + 'InstallSharedDir', + 'InstallSharedWOWDir', + 'InstanceDir' + ) } - if ($Action -eq 'AddNode') + if ($null -ne $BrowserSvcStartupType) { - if ($PSBoundParameters.ContainsKey('SQLSvcAccount')) - { - $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $SQLSvcAccount -ServiceType 'SQL') - } - - if($PSBoundParameters.ContainsKey('AgtSvcAccount')) - { - $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $AgtSvcAccount -ServiceType 'AGT') - } + $argumentVars += 'BrowserSvcStartupType' } if ($Features.Contains('SQLENGINE')) { - $argumentVars += @( - 'SecurityMode', - 'SQLCollation', - 'InstallSQLDataDir', - 'SQLUserDBDir', - 'SQLUserDBLogDir', - 'SQLTempDBDir', - 'SQLTempDBLogDir', - 'SQLBackupDir' - ) if ($PSBoundParameters.ContainsKey('SQLSvcAccount')) { @@ -1118,18 +1167,33 @@ function Set-TargetResource $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $AgtSvcAccount -ServiceType 'AGT') } - $setupArguments += @{ SQLSysAdminAccounts = @($SetupCredential.UserName) } - if ($PSBoundParameters.ContainsKey('SQLSysAdminAccounts')) + if ($SecurityMode -eq 'SQL') { - $setupArguments['SQLSysAdminAccounts'] += $SQLSysAdminAccounts + $setupArguments += @{ SAPwd = $SAPwd.GetNetworkCredential().Password } } - if ($SecurityMode -eq 'SQL') + # Should not be passed when PrepareFailoverCluster is specified + if ($Action -in @('Install','InstallFailoverCluster','CompleteFailoverCluster')) { - $setupArguments += @{ SAPwd = $SAPwd.GetNetworkCredential().Password } + $setupArguments += @{ SQLSysAdminAccounts = @($SetupCredential.UserName) } + if ($PSBoundParameters.ContainsKey('SQLSysAdminAccounts')) + { + $setupArguments['SQLSysAdminAccounts'] += $SQLSysAdminAccounts + } + + $argumentVars += @( + 'SecurityMode', + 'SQLCollation', + 'InstallSQLDataDir', + 'SQLUserDBDir', + 'SQLUserDBLogDir', + 'SQLTempDBDir', + 'SQLTempDBLogDir', + 'SQLBackupDir' + ) } - if ($Action -notin @('PrepareFailoverCluster','CompleteFailoverCluster','InstallFailoverCluster','AddNode')) + if ($Action -in @('Install')) { $setupArguments += @{ AgtSvcStartupType = 'Automatic' } } @@ -1167,11 +1231,14 @@ function Set-TargetResource $setupArguments += (Get-ServiceAccountParameters -ServiceAccount $ASSvcAccount -ServiceType 'AS') } - $setupArguments += @{ ASSysAdminAccounts = @($SetupCredential.UserName) } - - if($PSBoundParameters.ContainsKey("ASSysAdminAccounts")) + if ($Action -in ('Install','InstallFailoverCluster','CompleteFailoverCluster')) { - $setupArguments['ASSysAdminAccounts'] += $ASSysAdminAccounts + $setupArguments += @{ ASSysAdminAccounts = @($SetupCredential.UserName) } + + if($PSBoundParameters.ContainsKey("ASSysAdminAccounts")) + { + $setupArguments['ASSysAdminAccounts'] += $ASSysAdminAccounts + } } } @@ -1264,14 +1331,16 @@ function Set-TargetResource { $processArguments.Add('Credential',$SetupCredential) } + $process = StartWin32Process @processArguments New-VerboseMessage -Message $process + WaitForWin32ProcessEnd -Path $pathToSetupExecutable -Arguments $arguments if ($ForceReboot -or ($null -ne (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue))) { - if (!($SuppressReboot)) + if (-not ($SuppressReboot)) { $global:DSCMachineStatus = 1 } @@ -1281,7 +1350,7 @@ function Set-TargetResource } } - if (!(Test-TargetResource @PSBoundParameters)) + if (-not (Test-TargetResource @PSBoundParameters)) { throw New-TerminatingError -ErrorType TestFailedAfterSet -ErrorCategory InvalidResult } @@ -1629,11 +1698,10 @@ function Test-TargetResource $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters New-VerboseMessage -Message "Features found: '$($getTargetResourceResult.Features)'" - $result = $false + $result = $true + if ($getTargetResourceResult.Features ) { - $result = $true - foreach ($feature in $Features.Split(",")) { # Given that all the returned features are uppercase, make sure that the feature to search for is also uppercase @@ -1646,13 +1714,15 @@ function Test-TargetResource } } } + else + { + $result = $false + } if ($PSCmdlet.ParameterSetName -eq 'ClusterInstall') { New-VerboseMessage -Message "Clustered install, checking parameters." - $result = $true - $boundParameters.Keys | Where-Object {$_ -imatch "^FailoverCluster"} | ForEach-Object { $variableName = $_ @@ -1663,7 +1733,7 @@ function Test-TargetResource } } - $result + return $result } <# diff --git a/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.psm1 b/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.psm1 index 323889edb..aba84026c 100644 --- a/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.psm1 +++ b/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.psm1 @@ -1,126 +1,175 @@ -$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path -Write-Verbose -Message "CurrentPath: $currentPath" - -# Load Common Code -Import-Module $currentPath\..\..\xSQLServerHelper.psm1 -Verbose:$false -ErrorAction Stop - +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'xSQLServerHelper.psm1') ` + -Force + +<# + .SYNOPSIS + Returns the cluster role/group that is waiting to be created, + along with the time and number of times to wait. + + .PARAMETER Name + Name of the cluster role/group to look for (normally the same as the Availability Group name). + + .PARAMETER RetryIntervalSec + The interval, in seconds, to check for the presence of the cluster role/group. + Default value is 20 seconds. + When the cluster role/group has been found the resource will wait for this amount of time once + more before returning. + + .PARAMETER RetryCount + Maximum number of retries until the resource will timeout and throw an error. Default values is 30 times. +#> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Name, - - [UInt64] $RetryIntervalSec = 10, - [UInt32] $RetryCount = 50 + + [Parameter()] + [System.UInt64] + $RetryIntervalSec = 20, + + [Parameter()] + [System.UInt32] + $RetryCount = 30 ) - @{ + $clusterGroupFound = $false + + $clusterGroup = Get-ClusterGroup -Name $Name -ErrorAction SilentlyContinue + if ($null -ne $clusterGroup) + { + $clusterGroupFound = $true + } + + return @{ Name = $Name RetryIntervalSec = $RetryIntervalSec RetryCount = $RetryCount + GroupExist = $clusterGroupFound } } +<# + .SYNOPSIS + Waits for a cluster role/group to be created + + .PARAMETER Name + Name of the cluster role/group to look for (normally the same as the Availability Group name). + + .PARAMETER RetryIntervalSec + The interval, in seconds, to check for the presence of the cluster role/group. + Default value is 20 seconds. + When the cluster role/group has been found the resource will wait for this amount of time once + more before returning. + .PARAMETER RetryCount + Maximum number of retries until the resource will timeout and throw an error. Default values is 30 times. +#> function Set-TargetResource { [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Name, + [Parameter()] [System.UInt64] - $RetryIntervalSec =20, + $RetryIntervalSec = 20, + [Parameter()] [System.UInt32] - $RetryCount = 6 + $RetryCount = 30 ) - $AGFound = $false - New-VerboseMessage -Message "Checking for Availaibilty Group $Name ..." + New-VerboseMessage -Message "Checking for cluster group $Name. Will try for a total of $($RetryIntervalSec*$RetryCount) seconds." - for ($count = 0; $count -lt $RetryCount; $count++) + $getTargetResourceParameters = @{ + Name = $Name + RetryIntervalSec = $RetryIntervalSec + RetryCount = $RetryCount + } + + for ($forLoopCount = 0; $forLoopCount -lt $RetryCount; $forLoopCount++) { - try - { - $clusterGroup = Get-ClusterGroup -Name $Name -ErrorAction Ignore - - if ($clusterGroup -ne $null) - { - New-VerboseMessage -Message "Found Availability Group $Name" - $AGFound = $true - Start-Sleep -Seconds $RetryIntervalSec - break; - } - - } - catch + $clusterGroupFound = (Get-TargetResource @getTargetResourceParameters).GroupExist + if ($clusterGroupFound) { - New-VerboseMessage -Message "Availability Group $Name not found. Will retry again after $RetryIntervalSec sec" + New-VerboseMessage -Message "Found cluster group $Name. Will sleep for another $RetryIntervalSec seconds before continuing." + Start-Sleep -Seconds $RetryIntervalSec + break } - - New-VerboseMessage -Message "Availability Group $Name not found. Will retry again after $RetryIntervalSec sec" + + New-VerboseMessage -Message "Cluster group $Name not found. Will retry again after $RetryIntervalSec sec" Start-Sleep -Seconds $RetryIntervalSec } - if (! $AGFound) + if (-not $clusterGroupFound) { - throw "Availability Group $Name not found after $count attempts with $RetryIntervalSec sec interval" - Exit + throw "Cluster group $Name not found after $RetryCount attempts with $RetryIntervalSec sec interval" } +} +<# + .SYNOPSIS + Tests if the cluster role/group has been created. -} + .PARAMETER Name + Name of the cluster role/group to look for (normally the same as the Availability Group name). + .PARAMETER RetryIntervalSec + The interval, in seconds, to check for the presence of the cluster role/group. + Default value is 20 seconds. + When the cluster role/group has been found the resource will wait for this amount of time once + more before returning. + .PARAMETER RetryCount + Maximum number of retries until the resource will timeout and throw an error. Default values is 30 times. +#> function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Name, + [Parameter()] [System.UInt64] - $RetryIntervalSec = 10, + $RetryIntervalSec = 20, + [Parameter()] [System.UInt32] - $RetryCount = 50 + $RetryCount = 30 ) - New-VerboseMessage -Message "Checking for Availability Group $Name ..." + New-VerboseMessage -Message "Testing for cluster group $Name." - try - { - - $clusterGroup = Get-ClusterGroup -Name $Name -ErrorAction Ignore + $getTargetResourceParameters = @{ + Name = $Name + RetryIntervalSec = $RetryIntervalSec + RetryCount = $RetryCount + } - if ($clusterGroup -eq $null) - { - New-VerboseMessage -Message "Availability Group $Name not found" - $false - } - else - { - New-VerboseMessage -Message "Found Availabilty Group $Name" - $true - } + $clusterGroupFound = (Get-TargetResource @getTargetResourceParameters).GroupExist + if ($clusterGroupFound) + { + New-VerboseMessage -Message "Found cluster group $Name" } - catch + else { - New-VerboseMessage -Message "Availability Group $Name not found" - $false + New-VerboseMessage -Message "Cluster group $Name not found" } -} + return $clusterGroupFound +} Export-ModuleMember -Function *-TargetResource - diff --git a/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.schema.mof b/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.schema.mof index 08be80882..1562c36ef 100644 --- a/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.schema.mof +++ b/DSCResources/MSFT_xWaitForAvailabilityGroup/MSFT_xWaitForAvailabilityGroup.schema.mof @@ -1,9 +1,8 @@ - [ClassVersion("1.0.0.0"), FriendlyName("xWaitForAvailabilityGroup")] class MSFT_xWaitForAvailabilityGroup : OMI_BaseResource { - [Key, Description("Availability Group Name")] String Name; - [Write, Description("Interval to check for Availability Group")] Uint64 RetryIntervalSec; - [Write, Description("Maximum number of retries to check Availabilty group creation")] Uint32 RetryCount; + [Key, Description("Name of the cluster role/group to look for (normally the same as the Availability Group name).")] String Name; + [Write, Description("The interval, in seconds, to check for the presence of the cluster role/group. Default value is 20 seconds. When the cluster role/group has been found the resource will wait for this amount of time once more before returning.")] Uint64 RetryIntervalSec; + [Write, Description("Maximum number of retries until the resource will timeout and throw an error. Default values is 30 times.")] Uint32 RetryCount; + [Read, Description("Returns $true if the cluster role/group exist, otherwise it returns $false. Used by Get-TargetResource.")] Boolean GroupExist; }; - diff --git a/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroup/1-CreateAvailabilityGroup.ps1 b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroup/1-CreateAvailabilityGroup.ps1 index 6ca69084c..b21f94c97 100644 --- a/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroup/1-CreateAvailabilityGroup.ps1 +++ b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroup/1-CreateAvailabilityGroup.ps1 @@ -57,7 +57,6 @@ Configuration Example EndPointName = 'HADR' Ensure = 'Present' Port = 5022 - AuthorizedUser = 'sa' SQLServer = $Node.NodeName SQLInstanceName = $Node.SQLInstanceName PsDscRunAsCredential = $SysAdminAccount diff --git a/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1 b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1 new file mode 100644 index 000000000..0a88c49c0 --- /dev/null +++ b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1 @@ -0,0 +1,100 @@ +<# +.EXAMPLE + This example shows how to ensure that the Availability Group Replica 'SQL2' exists in the Availability Group 'TestAG'. +#> + +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName= '*' + SQLInstanceName = 'MSSQLSERVER' + AvailabilityGroupName = 'TestAG' + }, + + @{ + NodeName = 'SQL1' + Role = 'PrimaryReplica' + }, + + @{ + NodeName = 'SQL2' + Role = 'SecondaryReplica' + } + ) +} + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + Node $AllNodes.NodeName { + # Adding the required service account to allow the cluster to log into SQL + xSQLServerLogin AddNTServiceClusSvc + { + Ensure = 'Present' + Name = 'NT SERVICE\ClusSvc' + LoginType = 'WindowsUser' + SQLServer = $Node.NodeName + SQLInstanceName = $Node.SQLInstanceName + PsDscRunAsCredential = $SysAdminAccount + } + + # Add the required permissions to the cluster service login + xSQLServerPermission AddNTServiceClusSvcPermissions + { + DependsOn = '[xSQLServerLogin]AddNTServiceClusSvc' + Ensure = 'Present' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Principal = 'NT SERVICE\ClusSvc' + Permission = 'AlterAnyAvailabilityGroup','ViewServerState' + PsDscRunAsCredential = $SysAdminAccount + } + + # Create a DatabaseMirroring endpoint + xSQLServerEndpoint HADREndpoint + { + EndPointName = 'HADR' + Ensure = 'Present' + Port = 5022 + SQLServer = $Node.NodeName + SQLInstanceName = $Node.SQLInstanceName + PsDscRunAsCredential = $SysAdminAccount + } + + if ( $Node.Role -eq 'PrimaryReplica' ) + { + # Create the availability group on the instance tagged as the primary replica + xSQLServerAlwaysOnAvailabilityGroup AddTestAG + { + Ensure = 'Present' + Name = $Node.AvailabilityGroupName + SQLInstanceName = $Node.SQLInstanceName + SQLServer = $Node.NodeName + DependsOn = '[xSQLServerEndpoint]HADREndpoint','[xSQLServerPermission]AddNTServiceClusSvcPermissions' + PsDscRunAsCredential = $SysAdminAccount + } + } + + if ( $Node.Role -eq 'SecondaryReplica' ) + { + # Add the availability group replica to the availability group + xSQLServerAlwaysOnAvailabilityGroupReplica AddReplica + { + Ensure = 'Present' + Name = $Node.NodeName + AvailabilityGroupName = $Node.AvailabilityGroupName + SQLServer = $Node.NodeName + SQLInstanceName = $Node.SQLInstanceName + PrimaryReplicaSQLServer = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' } ).NodeName + PrimaryReplicaSQLInstanceName = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' } ).SQLInstanceName + } + } + } +} diff --git a/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/2-RemoveAvailabilityGroupReplica.ps1 b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/2-RemoveAvailabilityGroupReplica.ps1 new file mode 100644 index 000000000..a21148cc2 --- /dev/null +++ b/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/2-RemoveAvailabilityGroupReplica.ps1 @@ -0,0 +1,100 @@ +<# +.EXAMPLE + This example shows how to ensure that the Availability Group Replica 'SQL2' does not exist in the Availability Group 'TestAG'. +#> + +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName= '*' + SQLInstanceName = 'MSSQLSERVER' + AvailabilityGroupName = 'TestAG' + }, + + @{ + NodeName = 'SQL1' + Role = 'PrimaryReplica' + }, + + @{ + NodeName = 'SQL2' + Role = 'SecondaryReplica' + } + ) +} + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + Node $AllNodes.NodeName { + # Adding the required service account to allow the cluster to log into SQL + xSQLServerLogin AddNTServiceClusSvc + { + Ensure = 'Present' + Name = 'NT SERVICE\ClusSvc' + LoginType = 'WindowsUser' + SQLServer = $Node.NodeName + SQLInstanceName = $Node.SQLInstanceName + PsDscRunAsCredential = $SysAdminAccount + } + + # Add the required permissions to the cluster service login + xSQLServerPermission AddNTServiceClusSvcPermissions + { + DependsOn = '[xSQLServerLogin]AddNTServiceClusSvc' + Ensure = 'Present' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Principal = 'NT SERVICE\ClusSvc' + Permission = 'AlterAnyAvailabilityGroup','ViewServerState' + PsDscRunAsCredential = $SysAdminAccount + } + + # Create a DatabaseMirroring endpoint + xSQLServerEndpoint HADREndpoint + { + EndPointName = 'HADR' + Ensure = 'Present' + Port = 5022 + SQLServer = $Node.NodeName + SQLInstanceName = $Node.SQLInstanceName + PsDscRunAsCredential = $SysAdminAccount + } + + if ( $Node.Role -eq 'PrimaryReplica' ) + { + # Create the availability group on the instance tagged as the primary replica + xSQLServerAlwaysOnAvailabilityGroup AddTestAG + { + Ensure = 'Present' + Name = $Node.AvailabilityGroupName + SQLInstanceName = $Node.SQLInstanceName + SQLServer = $Node.NodeName + DependsOn = '[xSQLServerEndpoint]HADREndpoint','[xSQLServerPermission]AddNTServiceClusSvcPermissions' + PsDscRunAsCredential = $SysAdminAccount + } + } + + if ( $Node.Role -eq 'SecondaryReplica' ) + { + # Add the availability group replica to the availability group + xSQLServerAlwaysOnAvailabilityGroupReplica AddReplica + { + Ensure = 'Absent' + Name = $Node.NodeName + AvailabilityGroupName = $Node.AvailabilityGroupName + SQLServer = $Node.NodeName + SQLInstanceName = $Node.SQLInstanceName + PrimaryReplicaSQLServer = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' } ).NodeName + PrimaryReplicaSQLInstanceName = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' } ).SQLInstanceName + } + } + } +} diff --git a/Examples/Resources/xSQLServerAvailabilityGroupListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 b/Examples/Resources/xSQLServerAvailabilityGroupListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 new file mode 100644 index 000000000..3b9c3d94a --- /dev/null +++ b/Examples/Resources/xSQLServerAvailabilityGroupListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1 @@ -0,0 +1,32 @@ +<# +.EXAMPLE + This example will add an Availability Group listener with the same name as the cluster role VCO. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerAvailabilityGroupListener AvailabilityGroupListenerWithSameNameAsVCO + { + Ensure = 'Present' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AG-01' + Name = 'AG-01' + IpAddress = '192.168.0.73/255.255.255.0' + Port = 5301 + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerAvailabilityGroupListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 b/Examples/Resources/xSQLServerAvailabilityGroupListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 new file mode 100644 index 000000000..e3a1842eb --- /dev/null +++ b/Examples/Resources/xSQLServerAvailabilityGroupListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 @@ -0,0 +1,32 @@ +<# +.EXAMPLE + This example will add an Availability Group listener with a different than the cluster role VCO. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerAvailabilityGroupListener AvailabilityGroupListenerWithDifferentNameAsVCO + { + Ensure = 'Present' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AvailabilityGroup-01' + Name = 'AG-01' + IpAddress = '192.168.0.74/255.255.255.0' + Port = 5302 + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerAvailabilityGroupListener/3-RemoveAvailabilityGroupListenerWithSameNameAsVCO.ps1 b/Examples/Resources/xSQLServerAvailabilityGroupListener/3-RemoveAvailabilityGroupListenerWithSameNameAsVCO.ps1 new file mode 100644 index 000000000..fad376844 --- /dev/null +++ b/Examples/Resources/xSQLServerAvailabilityGroupListener/3-RemoveAvailabilityGroupListenerWithSameNameAsVCO.ps1 @@ -0,0 +1,30 @@ +<# +.EXAMPLE + This example will remove an Availability Group listener with a different name than cluster role VCO. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerAvailabilityGroupListener RemoveAvailabilityGroupListenerWithDifferentNameAsVCO + { + Ensure = 'Absent' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AvailabilityGroup-01' + Name = 'AG-01' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerAvailabilityGroupListener/4-RemoveAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 b/Examples/Resources/xSQLServerAvailabilityGroupListener/4-RemoveAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 new file mode 100644 index 000000000..6c545faa6 --- /dev/null +++ b/Examples/Resources/xSQLServerAvailabilityGroupListener/4-RemoveAvailabilityGroupListenerWithDifferentNameAsVCO.ps1 @@ -0,0 +1,30 @@ +<# +.EXAMPLE + This example will remove an Availability Group listener with the same name as the cluster role VCO. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerAvailabilityGroupListener RemoveAvailabilityGroupListenerWithSameNameAsVCO + { + Ensure = 'Absent' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AG-01' + Name = "AG-01" + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerAvailabilityGroupListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 b/Examples/Resources/xSQLServerAvailabilityGroupListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 new file mode 100644 index 000000000..6d9ae6796 --- /dev/null +++ b/Examples/Resources/xSQLServerAvailabilityGroupListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1 @@ -0,0 +1,32 @@ +<# +.EXAMPLE + This example will add an Availability Group listener using DHCP on the default server subnet. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerAvailabilityGroupListener AvailabilityGroupListenerWithSameNameAsVCO + { + Ensure = 'Present' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AG-01' + Name = 'AG-01' + DHCP = $true # Also not specifing parameter DHCP will default to using DHCP with the default server subnet. + Port = 5301 + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerAvailabilityGroupListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 b/Examples/Resources/xSQLServerAvailabilityGroupListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 new file mode 100644 index 000000000..09de5f0a3 --- /dev/null +++ b/Examples/Resources/xSQLServerAvailabilityGroupListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1 @@ -0,0 +1,33 @@ +<# +.EXAMPLE + This example will add an Availability Group listener using DHCP with a specific subnet. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerAvailabilityGroupListener AvailabilityGroupListenerWithSameNameAsVCO + { + Ensure = 'Present' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + AvailabilityGroup = 'AG-01' + Name = 'AG-01' + DHCP = $true + IpAddress = '192.168.0.1/255.255.252.0' + Port = 5301 + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerConfiguration/1-Configure2DBInstances.ps1 b/Examples/Resources/xSQLServerConfiguration/1-Configure2DBInstances.ps1 deleted file mode 100644 index 65851375f..000000000 --- a/Examples/Resources/xSQLServerConfiguration/1-Configure2DBInstances.ps1 +++ /dev/null @@ -1,33 +0,0 @@ -<# -.EXAMPLE - This example shows how to configure 2 DB Instances on the same server to have CLR enabled. -#> - -$configurationData = @{ - AllNodes = @( - @{ - NodeName = "localhost" - SQLInstances = @("CONTENT", "DIST") - OptionName = "clr enabled" - } - ) -} - -Configuration Example -{ - Import-DscResource -ModuleName xSqlServer - - node localhost { - - foreach ($SQLInstance in $node.SQLInstances) { - - xSQLServerConfiguration ('SQLConfigCLR_{0}' -f $SQLInstance) { - - SQLServer = $node.NodeName - SQLInstanceName = $SQLInstance - OptionName = $node.OptionName - OptionValue = 1 - } - } - } -} diff --git a/Examples/Resources/xSQLServerConfiguration/1-ConfigureTwoInstancesOnTheSameServerToEnableClr.ps1 b/Examples/Resources/xSQLServerConfiguration/1-ConfigureTwoInstancesOnTheSameServerToEnableClr.ps1 new file mode 100644 index 000000000..cf8b32af9 --- /dev/null +++ b/Examples/Resources/xSQLServerConfiguration/1-ConfigureTwoInstancesOnTheSameServerToEnableClr.ps1 @@ -0,0 +1,35 @@ +<# +.EXAMPLE + This example shows how to configure two SQL Server instances on the same server to have CLR enabled. +.NOTES + To get all available options run sp_configure on the SQL Server instance, or refer to https://msdn.microsoft.com/en-us/library/ms189631.aspx +#> + +$configurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + SQLInstances = @('CONTENT', 'DIST') + OptionName = 'clr enabled' + } + ) +} + +Configuration Example +{ + Import-DscResource -ModuleName xSqlServer + + node localhost + { + foreach ($SQLInstance in $node.SQLInstances) + { + xSQLServerConfiguration ('SQLConfigCLR_{0}' -f $SQLInstance) + { + SQLServer = $node.NodeName + SQLInstanceName = $SQLInstance + OptionName = $node.OptionName + OptionValue = 1 + } + } + } +} diff --git a/Examples/Resources/xSQLServerConfiguration/2-ConfigureInstanceToEnablePriorityBoost.ps1 b/Examples/Resources/xSQLServerConfiguration/2-ConfigureInstanceToEnablePriorityBoost.ps1 new file mode 100644 index 000000000..3bba761a3 --- /dev/null +++ b/Examples/Resources/xSQLServerConfiguration/2-ConfigureInstanceToEnablePriorityBoost.ps1 @@ -0,0 +1,24 @@ +<# +.EXAMPLE + This example shows how to configure two SQL Server instances on the same server to have the setting 'priority boost' enabled. +.NOTES + To get all available options run sp_configure on the SQL Server instance, or refer to https://msdn.microsoft.com/en-us/library/ms189631.aspx +#> + +Configuration Example +{ + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerConfiguration 'SQLConfigPriorityBoost' + { + + SQLServer = 'localhost' + SQLInstanceName = 'MSSQLSERVER' + OptionName = 'priority boost' + OptionValue = 1 + RestartService = $false + } + } +} diff --git a/Examples/Resources/xSQLServerDatabaseRole/1-AddDatabaseRole.ps1 b/Examples/Resources/xSQLServerDatabaseRole/1-AddDatabaseRole.ps1 new file mode 100644 index 000000000..553e4ae8e --- /dev/null +++ b/Examples/Resources/xSQLServerDatabaseRole/1-AddDatabaseRole.ps1 @@ -0,0 +1,31 @@ +<# +.EXAMPLE + This example shows how to ensure that the user account CONTOSO\SQLAdmin + has "MyRole" and "MySecondRole" SQL database roles. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerDatabaseRole Add_Database_Role + { + Ensure = 'Present' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + Name = 'CONTOSO\SQLAdmin' + Role = 'MyRole','MySecondRole' + Database = 'AdventureWorks' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerDatabaseRole/2-RemoveDatabaseRole.ps1 b/Examples/Resources/xSQLServerDatabaseRole/2-RemoveDatabaseRole.ps1 new file mode 100644 index 000000000..480b90f5f --- /dev/null +++ b/Examples/Resources/xSQLServerDatabaseRole/2-RemoveDatabaseRole.ps1 @@ -0,0 +1,31 @@ +<# +.EXAMPLE + This example shows how to ensure that the user account CONTOSO\SQLAdmin + is not member of the "DeleteRole" SQL database role. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerDatabaseRole Remove_Database_Role + { + Ensure = 'Absent' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + Name = 'CONTOSO\SQLAdmin' + Role = 'DeleteRole' + Database = 'AdventureWorks' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpoint/1-CreateEndpointWithDefaultValues.ps1 b/Examples/Resources/xSQLServerEndpoint/1-CreateEndpointWithDefaultValues.ps1 new file mode 100644 index 000000000..55aafd2be --- /dev/null +++ b/Examples/Resources/xSQLServerEndpoint/1-CreateEndpointWithDefaultValues.ps1 @@ -0,0 +1,36 @@ +<# + .EXAMPLE + This example will add a Database Mirror endpoint, to two instances, using the default values. + +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xSQLServerEndpoint SQLConfigureEndpoint-Instance1 + { + EndpointName = 'HADR' + SQLInstanceName = 'INST1' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerEndpoint SQLConfigureEndpoint-Instances2 + { + EndpointName = 'HADR' + SQLInstanceName = 'INST2' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpoint/2-CreateEndpointWithSpecificPortAndIPAddress.ps1 b/Examples/Resources/xSQLServerEndpoint/2-CreateEndpointWithSpecificPortAndIPAddress.ps1 new file mode 100644 index 000000000..946fd98fa --- /dev/null +++ b/Examples/Resources/xSQLServerEndpoint/2-CreateEndpointWithSpecificPortAndIPAddress.ps1 @@ -0,0 +1,33 @@ +<# + .EXAMPLE + This example will add a Database Mirror endpoint with a specific listener port and a specific listener IP address. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xSQLServerEndpoint SQLConfigureEndpoint + { + Ensure = 'Present' + + EndpointName = 'HADR' + Port = 9001 + IpAddress = '192.168.0.20' + + SQLServer = 'server1.company.local' + SQLInstanceName = 'INST1' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpoint/3-RemoveEndpoint.ps1 b/Examples/Resources/xSQLServerEndpoint/3-RemoveEndpoint.ps1 new file mode 100644 index 000000000..068353629 --- /dev/null +++ b/Examples/Resources/xSQLServerEndpoint/3-RemoveEndpoint.ps1 @@ -0,0 +1,40 @@ +<# + .EXAMPLE + This example will remove an Database Mirror endpoint from two instances. + +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xSQLServerEndpoint SQLConfigureEndpoint-Instance1 + { + Ensure = 'Absent' + + EndpointName = 'HADR' + SQLInstanceName = 'INST1' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerEndpoint SQLConfigureEndpoint-Instance2 + { + Ensure = 'Absent' + + EndpointName = 'HADR' + SQLInstanceName = 'INST2' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpointPermission/1-AddConnectPermission.ps1 b/Examples/Resources/xSQLServerEndpointPermission/1-AddConnectPermission.ps1 new file mode 100644 index 000000000..b3aa1bf86 --- /dev/null +++ b/Examples/Resources/xSQLServerEndpointPermission/1-AddConnectPermission.ps1 @@ -0,0 +1,37 @@ +<# + .EXAMPLE + This example will add connect permission to the credentials provided in $SqlServiceCredential + to the endpoint named 'DefaultMirrorEndpoint'. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount, + + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceCredential + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xSQLServerEndpointPermission SQLConfigureEndpointPermission + { + Ensure = 'Present' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceCredential.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpointPermission/2-RemoveConnectPermission.ps1 b/Examples/Resources/xSQLServerEndpointPermission/2-RemoveConnectPermission.ps1 new file mode 100644 index 000000000..cd7a20e28 --- /dev/null +++ b/Examples/Resources/xSQLServerEndpointPermission/2-RemoveConnectPermission.ps1 @@ -0,0 +1,37 @@ +<# + .EXAMPLE + This example will add connect permission to the credentials provided in $SqlServiceCredential + to the endpoint named 'DefaultMirrorEndpoint'. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount, + + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceCredential + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xSQLServerEndpointPermission SQLConfigureEndpointPermission + { + Ensure = 'Absent' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceCredential.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpointPermission/3-AddConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 b/Examples/Resources/xSQLServerEndpointPermission/3-AddConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 new file mode 100644 index 000000000..d62e9ebff --- /dev/null +++ b/Examples/Resources/xSQLServerEndpointPermission/3-AddConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 @@ -0,0 +1,101 @@ +<# + .EXAMPLE + This example will add connect permission to both an Always On primary replica and an + Always On secondary replica, and where each replica has a different SQL service account. +#> + +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName= '*' + SqlInstanceName = 'MSSQLSERVER' + }, + + @{ + NodeName = 'SQLNODE01.company.local' + Role = 'PrimaryReplica' + }, + + @{ + NodeName = 'SQLNODE02.company.local' + Role = 'SecondaryReplica' + } + ) +} + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount, + + [Parameter(Mandatory=$true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceNode1Credential, + + [Parameter(Mandatory=$true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceNode2Credential + ) + + Import-DscResource -ModuleName xSQLServer + + node $AllNodes.Where{$_.Role -eq 'PrimaryReplica' }.NodeName + { + xSQLServerEndpointPermission SQLConfigureEndpointPermissionPrimary + { + Ensure = 'Present' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceNode1Credential.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerEndpointPermission SQLConfigureEndpointPermissionSecondary + { + Ensure = 'Present' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceNode2Credential.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SysAdminAccount + } + } + + Node $AllNodes.Where{ $_.Role -eq 'SecondaryReplica' }.NodeName + { + xSQLServerEndpointPermission SQLConfigureEndpointPermissionPrimary + { + Ensure = 'Present' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceNode1Credential.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerEndpointPermission SQLConfigureEndpointPermissionSecondary + { + Ensure = 'Present' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceNode2Credential.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpointPermission/4-RemoveConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 b/Examples/Resources/xSQLServerEndpointPermission/4-RemoveConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 new file mode 100644 index 000000000..b75f43219 --- /dev/null +++ b/Examples/Resources/xSQLServerEndpointPermission/4-RemoveConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1 @@ -0,0 +1,109 @@ +<# + .EXAMPLE + This example will remove connect permission to both an Always On primary replica and an + Always On secondary replica, and where each replica has a different SQL service account. +#> + +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName= '*' + SqlInstanceName = 'MSSQLSERVER' + }, + + @{ + NodeName = 'SQLNODE01.company.local' + Role = 'PrimaryReplica' + }, + + @{ + NodeName = 'SQLNODE02.company.local' + Role = 'SecondaryReplica' + } + ) +} + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount, + + [Parameter(Mandatory=$true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceNode1Credential, + + [Parameter(Mandatory=$true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceNode2Credential + ) + + Import-DscResource -ModuleName xSQLServer + + node $AllNodes.Where{$_.Role -eq 'PrimaryReplica' }.NodeName + { + xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionPrimary + { + Ensure = 'Absent' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceCredentialNode2.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SqlAdministratorCredential + + DependsOn = '[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionPrimary' + } + + xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionSecondary + { + Ensure = 'Absent' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceCredentialNode2.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SqlAdministratorCredential + + DependsOn = '[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionSecondary' + } + } + + Node $AllNodes.Where{ $_.Role -eq 'SecondaryReplica' }.NodeName + { + xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionPrimary + { + Ensure = 'Absent' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceCredentialNode2.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SqlAdministratorCredential + + DependsOn = '[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionPrimary' + } + + xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionSecondary + { + Ensure = 'Absent' + NodeName = $Node.NodeName + InstanceName = $Node.SqlInstanceName + Name = 'DefaultMirrorEndpoint' + Principal = $SqlServiceCredentialNode2.UserName + Permission = 'CONNECT' + + PsDscRunAsCredential = $SqlAdministratorCredential + + DependsOn = '[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionSecondary' + } + } +} diff --git a/Examples/Resources/xSQLServerEndpointState/1-MakeSureEndpointIsStarted.ps1 b/Examples/Resources/xSQLServerEndpointState/1-MakeSureEndpointIsStarted.ps1 new file mode 100644 index 000000000..d6ef85419 --- /dev/null +++ b/Examples/Resources/xSQLServerEndpointState/1-MakeSureEndpointIsStarted.ps1 @@ -0,0 +1,61 @@ +<# + .EXAMPLE + This example will make sure that the endpoint DefaultMirrorEndpoint is in started state in the default instance, if not it will start the endpoint. + + .EXAMPLE + This example will make sure that the endpoint HADR is in started state in the default instance, if not it will start the endpoint. + + .EXAMPLE + This example will make sure that the endpoint DefaultMirrorEndpoint is in started state in the named instance INSTANCE1, if not it will start the endpoint. + + .NOTES + There is three different scenarios in this example to validate the schema during unit testing. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + # Start the DefaultMirrorEndpoint in the default instance + xSQLServerEndpointState StartEndpoint1 + { + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + Name = 'DefaultMirrorEndpoint' + State = 'Started' + + PsDscRunAsCredential = $SysAdminAccount + } + + # Start the HADR in the default instance + xSQLServerEndpointState StartEndpoint2 + { + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + Name = 'HADR' + State = 'Started' + + PsDscRunAsCredential = $SysAdminAccount + } + + # Start the DefaultMirrorEndpoint in the named instance INSTANCE1 + xSQLServerEndpointState StartEndpoint3 + { + NodeName = 'SQLNODE01.company.local' + InstanceName = 'INSTANCE1' + Name = 'DefaultMirrorEndpoint' + State = 'Started' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerEndpointState/2-MakeSureEndpointIsStopped.ps1 b/Examples/Resources/xSQLServerEndpointState/2-MakeSureEndpointIsStopped.ps1 new file mode 100644 index 000000000..998c33415 --- /dev/null +++ b/Examples/Resources/xSQLServerEndpointState/2-MakeSureEndpointIsStopped.ps1 @@ -0,0 +1,30 @@ +<# + .EXAMPLE + This example will make sure that the endpoint DefaultMirrorEndpoint is in stopped state, if not it will stop the endpoint. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xSQLServerEndpointState StopEndpoint + { + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + Name = 'DefaultMirrorEndpoint' + State = 'Stopped' + + PsDscRunAsCredential = $SysAdminAccount + + } + } +} diff --git a/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 b/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 index b844a81fd..9003d8359 100644 --- a/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 +++ b/Examples/Resources/xSQLServerLogin/1-AddLogin.ps1 @@ -34,6 +34,17 @@ Configuration Example PsDscRunAsCredential = $SysAdminAccount } + xSQLServerLogin Add_DisabledWindowsUser + { + Ensure = 'Present' + Name = 'CONTOSO\WindowsUser2' + LoginType = 'WindowsUser' + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + Disabled = $true + } + xSQLServerLogin Add_WindowsGroup { Ensure = 'Present' diff --git a/Examples/Resources/xSQLServerNetwork/1-EnableTcpIpWithStaticPort.ps1 b/Examples/Resources/xSQLServerNetwork/1-EnableTcpIpWithStaticPort.ps1 new file mode 100644 index 000000000..d19e6bf6e --- /dev/null +++ b/Examples/Resources/xSQLServerNetwork/1-EnableTcpIpWithStaticPort.ps1 @@ -0,0 +1,30 @@ +<# +.EXAMPLE + This example will enable TCP/IP protocol and set the custom static port to 4509. + When RestartService is set to $true the resource will also restart the SQL service. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerNetwork 'ChangeTcpIpOnDefaultInstance' + { + InstanceName = 'MSSQLSERVER' + ProtocolName = 'Tcp' + IsEnabled = $true + TCPDynamicPorts = '' + TCPPort = 4509 + RestartService = $true + } + } +} diff --git a/Examples/Resources/xSQLServerNetwork/2-EnableTcpIpWithDynamicPort.ps1 b/Examples/Resources/xSQLServerNetwork/2-EnableTcpIpWithDynamicPort.ps1 new file mode 100644 index 000000000..c42159d5e --- /dev/null +++ b/Examples/Resources/xSQLServerNetwork/2-EnableTcpIpWithDynamicPort.ps1 @@ -0,0 +1,29 @@ +<# +.EXAMPLE + This example will enable TCP/IP protocol and set the custom static port to 4509. + When RestartService is set to $true the resource will also restart the SQL service. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerNetwork 'ChangeTcpIpOnDefaultInstance' + { + InstanceName = 'MSSQLSERVER' + ProtocolName = 'Tcp' + IsEnabled = $true + TCPDynamicPorts = '0' + RestartService = $true + } + } +} diff --git a/Examples/Resources/xSQLServerPermission/1-AddServerPermissionForLogin.ps1 b/Examples/Resources/xSQLServerPermission/1-AddServerPermissionForLogin.ps1 new file mode 100644 index 000000000..5fd120204 --- /dev/null +++ b/Examples/Resources/xSQLServerPermission/1-AddServerPermissionForLogin.ps1 @@ -0,0 +1,43 @@ +<# + .EXAMPLE + This example will add the server permissions AlterAnyAvailabilityGroup and ViewServerState + to the login 'NT AUTHORITY\SYSTEM' and 'NT SERVICE\ClusSvc' to the default instance. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + # Add permission + xSQLServerPermission 'SQLConfigureServerPermission-SYSTEM' + { + Ensure = 'Present' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + Principal = 'NT AUTHORITY\SYSTEM' + Permission = 'AlterAnyAvailabilityGroup','ViewServerState' + + PsDscRunAsCredential = $SysAdminAccount + } + + xSQLServerPermission 'SQLConfigureServerPermission-ClusSvc' + { + Ensure = 'Present' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + Principal = 'NT SERVICE\ClusSvc' + Permission = 'AlterAnyAvailabilityGroup','ViewServerState' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerPermission/2-RemoveServerPermissionForLogin.ps1 b/Examples/Resources/xSQLServerPermission/2-RemoveServerPermissionForLogin.ps1 new file mode 100644 index 000000000..c6293ff58 --- /dev/null +++ b/Examples/Resources/xSQLServerPermission/2-RemoveServerPermissionForLogin.ps1 @@ -0,0 +1,32 @@ +<# + .EXAMPLE + This example will remove the server permissions AlterAnyAvailabilityGroup and ViewServerState + from the login 'NT AUTHORITY\SYSTEM'. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + # Add permission + xSQLServerPermission SQLConfigureServerPermission + { + Ensure = 'Absent' + NodeName = 'SQLNODE01.company.local' + InstanceName = 'MSSQLSERVER' + Principal = 'NT AUTHORITY\SYSTEM' + Permission = 'AlterAnyAvailabilityGroup','ViewServerState' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerReplication/1-ConfigureInstanceAsDistributor.ps1 b/Examples/Resources/xSQLServerReplication/1-ConfigureInstanceAsDistributor.ps1 new file mode 100644 index 000000000..b373b538e --- /dev/null +++ b/Examples/Resources/xSQLServerReplication/1-ConfigureInstanceAsDistributor.ps1 @@ -0,0 +1,30 @@ +<# +.EXAMPLE + This example shows how to configure a SQL Server instance as the distributor. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerReplication distributor + { + Ensure = 'Present' + InstanceName = 'MSSQLSERVER' + AdminLinkCredentials = $SysAdminAccount + DistributorMode = 'Local' + WorkingDirectory = 'C:\Temp' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerReplication/2-ConfigureInstanceAsPublisher.ps1 b/Examples/Resources/xSQLServerReplication/2-ConfigureInstanceAsPublisher.ps1 new file mode 100644 index 000000000..2498ef856 --- /dev/null +++ b/Examples/Resources/xSQLServerReplication/2-ConfigureInstanceAsPublisher.ps1 @@ -0,0 +1,31 @@ +<# +.EXAMPLE + This example shows how to configure a SQL Server instance as the publisher. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost + { + xSQLServerReplication publisher + { + Ensure = 'Present' + InstanceName = 'PUBLISHER' + AdminLinkCredentials = $SysAdminAccount + DistributorMode = 'Remote' + RemoteDistributor = 'distsqlsrv.company.local' + WorkingDirectory = 'C:\Temp' + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 b/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 index 6ee078eb1..f743efe45 100644 --- a/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 +++ b/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1 @@ -1,7 +1,7 @@ <# .EXAMPLE - This example shows how to ensure that the user account CONTOSO\SQLAdmin - has "dbcreator" and "securityadmin" SQL server roles. + This example shows how to ensure that the server role named + AdminSqlforBI is present on instance SQLServer\DSC. #> Configuration Example @@ -15,25 +15,13 @@ Configuration Example Import-DscResource -ModuleName xSqlServer node localhost { - xSQLServerLogin Add_LoginForSQLAdmin + xSQLServerRole Add_ServerRole_AdminSqlforBI { - 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 + Ensure = 'Present' + ServerRoleName = "AdminSqlforBI" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } } diff --git a/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 b/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 index c39e89c6b..824a9e997 100644 --- a/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 +++ b/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1 @@ -1,7 +1,7 @@ <# .EXAMPLE - This example shows how to ensure that the user account CONTOSO\SQLUser - does not have "setupadmin" SQL server role. + This example shows how to ensure that the server role named + serverRoleToDelete is not present on instance SQLServer\DSC. #> Configuration Example @@ -15,14 +15,13 @@ Configuration Example Import-DscResource -ModuleName xSqlServer node localhost { - xSQLServerRole Remove_ServerRoleFromLogin + xSQLServerRole Remove_ServerRole { - Ensure = 'Absent' - Name = 'CONTOSO\SQLUser' - ServerRole = "setupadmin" - SQLServer = 'SQLServer' - SQLInstanceName = 'DSC' - PsDscRunAsCredential = $SysAdminAccount + Ensure = 'Absent' + ServerRoleName = "serverRoleToDelete" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount } } } diff --git a/Examples/Resources/xSQLServerRole/3-AddMembersToServerRole.ps1 b/Examples/Resources/xSQLServerRole/3-AddMembersToServerRole.ps1 new file mode 100644 index 000000000..9c37866d4 --- /dev/null +++ b/Examples/Resources/xSQLServerRole/3-AddMembersToServerRole.ps1 @@ -0,0 +1,29 @@ +<# +.EXAMPLE + This example shows how to ensure that the server role named + AdminSqlforBI is present on instance SQLServer\DSC and only logins + CONTOSO\SQLAdmin and CONTOSO\SQLAdminBI are members of this role. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerRole Add_ServerRole_AdminSqlforBI + { + Ensure = 'Present' + ServerRoleName = 'AdminSqlforBI' + Members = "CONTOSO\SQLAdmin","CONTOSO\SQLAdminBI" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerRole/4-MembersToIncludeInServerRole.ps1 b/Examples/Resources/xSQLServerRole/4-MembersToIncludeInServerRole.ps1 new file mode 100644 index 000000000..8574a7d50 --- /dev/null +++ b/Examples/Resources/xSQLServerRole/4-MembersToIncludeInServerRole.ps1 @@ -0,0 +1,29 @@ +<# +.EXAMPLE + This example shows how to ensure that the server role named + AdminSqlforBI is present on instance SQLServer\DSC and logins + CONTOSO\John and CONTOSO\Kelly are added as members of this role. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerRole Add_ServerRole_AdminSqlforBI + { + Ensure = 'Present' + ServerRoleName = 'AdminSqlforBI' + MembersToInclude = "CONTOSO\John","CONTOSO\Kelly" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerRole/5-MembersToExcludeInServerRole.ps1 b/Examples/Resources/xSQLServerRole/5-MembersToExcludeInServerRole.ps1 new file mode 100644 index 000000000..5c5546735 --- /dev/null +++ b/Examples/Resources/xSQLServerRole/5-MembersToExcludeInServerRole.ps1 @@ -0,0 +1,29 @@ +<# +.EXAMPLE + This example shows how to ensure that the server role named + AdminSqlforBI is present on instance SQLServer\DSC and logins + CONTOSO\Mark and CONTOSO\Lucy are not members of this role. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSqlServer + + node localhost { + xSQLServerRole Drop_ServerRole_AdminSqlforBI + { + Ensure = 'Present' + ServerRoleName = 'AdminSqlforBI' + MembersToExclude = "CONTOSO\Mark","CONTOSO\Lucy" + SQLServer = 'SQLServer' + SQLInstanceName = 'DSC' + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xSQLServerSetup/1-InstallDefaultInstanceSingleServer.ps1 b/Examples/Resources/xSQLServerSetup/1-InstallDefaultInstanceSingleServer.ps1 new file mode 100644 index 000000000..aee8b02c7 --- /dev/null +++ b/Examples/Resources/xSQLServerSetup/1-InstallDefaultInstanceSingleServer.ps1 @@ -0,0 +1,89 @@ +<# + .EXAMPLE + This example shows how to install a default instance of SQL Server on a single server. + .NOTES + SQL Server setup is run using the SYSTEM account. Even if SetupCredential is provided + it is not used to install SQL Server at this time (see issue #139). +#> +Configuration Example +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlInstallCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAdministratorCredential = $SqlInstallCredential, + + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAgentServiceCredential = $SqlServiceCredential + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + #region Install prerequisites for SQL Server + WindowsFeature 'NetFramework35' { + Name = 'NET-Framework-Core' + Source = '\\fileserver.company.local\images$\Win2k12R2\Sources\Sxs' # Assumes built-in Everyone has read permission to the share and path. + Ensure = 'Present' + } + + WindowsFeature 'NetFramework45' { + Name = 'NET-Framework-45-Core' + Ensure = 'Present' + } + #endregion Install prerequisites for SQL Server + + #region Install SQL Server + xSQLServerSetup 'InstallDefaultInstance' + { + InstanceName = 'MSSQLSERVER' + Features = 'SQLENGINE,AS' + SQLCollation = 'SQL_Latin1_General_CP1_CI_AS' + SQLSvcAccount = $SqlServiceCredential + AgtSvcAccount = $SqlAgentServiceCredential + ASSvcAccount = $SqlServiceCredential + SQLSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + ASSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + SetupCredential = $SqlInstallCredential + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' + InstanceDir = 'C:\Program Files\Microsoft SQL Server' + InstallSQLDataDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Data' + SQLUserDBDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Data' + SQLUserDBLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Data' + SQLTempDBDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Data' + SQLTempDBLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Data' + SQLBackupDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Backup' + ASConfigDir = 'C:\MSOLAP\Config' + ASDataDir = 'C:\MSOLAP\Data' + ASLogDir = 'C:\MSOLAP\Log' + ASBackupDir = 'C:\MSOLAP\Backup' + ASTempDir = 'C:\MSOLAP\Temp' + SourcePath = 'C:\InstallMedia\SQL2016RTM' + UpdateEnabled = 'False' + ForceReboot = $false + + DependsOn = '[WindowsFeature]NetFramework35','[WindowsFeature]NetFramework45' + } + #endregion Install SQL Server + } +} diff --git a/Examples/Resources/xSQLServerSetup/2-InstallNamedInstanceSingleServer.ps1 b/Examples/Resources/xSQLServerSetup/2-InstallNamedInstanceSingleServer.ps1 new file mode 100644 index 000000000..d0c643e09 --- /dev/null +++ b/Examples/Resources/xSQLServerSetup/2-InstallNamedInstanceSingleServer.ps1 @@ -0,0 +1,90 @@ +<# + .EXAMPLE + This example shows how to install a named instance of SQL Server on a single server. + .NOTES + SQL Server setup is run using the SYSTEM account. Even if SetupCredential is provided + it is not used to install SQL Server at this time (see issue #139). +#> +Configuration Example +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlInstallCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAdministratorCredential = $SqlInstallCredential, + + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAgentServiceCredential = $SqlServiceCredential + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + #region Install prerequisites for SQL Server + WindowsFeature 'NetFramework35' { + Name = 'NET-Framework-Core' + Source = '\\fileserver.company.local\images$\Win2k12R2\Sources\Sxs' # Assumes built-in Everyone has read permission to the share and path. + Ensure = 'Present' + } + + WindowsFeature 'NetFramework45' { + Name = 'NET-Framework-45-Core' + Ensure = 'Present' + } + #endregion Install prerequisites for SQL Server + + #region Install SQL Server + xSQLServerSetup 'InstallNamedInstance-INST2016' + { + InstanceName = 'INST2016' + Features = 'SQLENGINE,AS' + SQLCollation = 'SQL_Latin1_General_CP1_CI_AS' + SQLSvcAccount = $SqlServiceCredential + AgtSvcAccount = $SqlAgentServiceCredential + ASSvcAccount = $SqlServiceCredential + SQLSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + ASSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + SetupCredential = $SqlInstallCredential + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' + InstanceDir = 'C:\Program Files\Microsoft SQL Server' + InstallSQLDataDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLUserDBDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLUserDBLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLTempDBDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLTempDBLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLBackupDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Backup' + ASConfigDir = 'C:\MSOLAP13.INST2016\Config' + ASDataDir = 'C:\MSOLAP13.INST2016\Data' + ASLogDir = 'C:\MSOLAP13.INST2016\Log' + ASBackupDir = 'C:\MSOLAP13.INST2016\Backup' + ASTempDir = 'C:\MSOLAP13.INST2016\Temp' + SourcePath = 'C:\InstallMedia\SQL2016RTM' + UpdateEnabled = 'False' + ForceReboot = $false + BrowserSvcStartupType = 'Automatic' + + DependsOn = '[WindowsFeature]NetFramework35','[WindowsFeature]NetFramework45' + } + #endregion Install SQL Server + } +} diff --git a/Examples/Resources/xSQLServerSetup/3-InstallNamedInstanceSingleServerFromUncPathUsingSourceCredential.ps1 b/Examples/Resources/xSQLServerSetup/3-InstallNamedInstanceSingleServerFromUncPathUsingSourceCredential.ps1 new file mode 100644 index 000000000..ef94bab9a --- /dev/null +++ b/Examples/Resources/xSQLServerSetup/3-InstallNamedInstanceSingleServerFromUncPathUsingSourceCredential.ps1 @@ -0,0 +1,95 @@ +<# + .EXAMPLE + This example shows how to install a named instance of SQL Server on a single server, from an UNC path. + .NOTES + Assumes the credentials assigned to SourceCredential have read permission on the share and on the UNC path. + The media will be copied locally, using impersonation with the credentials provided in SourceCredential, so + that the SYSTEM account can access the media locally. + + SQL Server setup is run using the SYSTEM account. Even if SetupCredential is provided + it is not used to install SQL Server at this time (see issue #139). +#> +Configuration Example +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlInstallCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAdministratorCredential = $SqlInstallCredential, + + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAgentServiceCredential = $SqlServiceCredential + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + #region Install prerequisites for SQL Server + WindowsFeature 'NetFramework35' { + Name = 'NET-Framework-Core' + Source = '\\fileserver.company.local\images$\Win2k12R2\Sources\Sxs' # Assumes built-in Everyone has read permission to the share and path. + Ensure = 'Present' + } + + WindowsFeature 'NetFramework45' { + Name = 'NET-Framework-45-Core' + Ensure = 'Present' + } + #endregion Install prerequisites for SQL Server + + #region Install SQL Server + xSQLServerSetup 'InstallNamedInstance-INST2016' + { + InstanceName = 'INST2016' + Features = 'SQLENGINE,AS' + SQLCollation = 'SQL_Latin1_General_CP1_CI_AS' + SQLSvcAccount = $SqlServiceCredential + AgtSvcAccount = $SqlAgentServiceCredential + ASSvcAccount = $SqlServiceCredential + SQLSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + ASSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + SetupCredential = $SqlInstallCredential + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' + InstanceDir = 'C:\Program Files\Microsoft SQL Server' + InstallSQLDataDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLUserDBDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLUserDBLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLTempDBDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLTempDBLogDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Data' + SQLBackupDir = 'C:\Program Files\Microsoft SQL Server\MSSQL13.INST2016\MSSQL\Backup' + ASConfigDir = 'C:\MSOLAP13.INST2016\Config' + ASDataDir = 'C:\MSOLAP13.INST2016\Data' + ASLogDir = 'C:\MSOLAP13.INST2016\Log' + ASBackupDir = 'C:\MSOLAP13.INST2016\Backup' + ASTempDir = 'C:\MSOLAP13.INST2016\Temp' + SourcePath = '\\fileserver.compant.local\images$\SQL2016RTM' + SourceCredential = $SqlInstallCredential + UpdateEnabled = 'False' + ForceReboot = $false + BrowserSvcStartupType = 'Automatic' + + DependsOn = '[WindowsFeature]NetFramework35','[WindowsFeature]NetFramework45' + } + #endregion Install SQL Server + } +} diff --git a/Examples/Resources/xSQLServerSetup/4-InstallNamedInstanceInFailoverClusterFirstNode.ps1 b/Examples/Resources/xSQLServerSetup/4-InstallNamedInstanceInFailoverClusterFirstNode.ps1 new file mode 100644 index 000000000..c90ac06b0 --- /dev/null +++ b/Examples/Resources/xSQLServerSetup/4-InstallNamedInstanceInFailoverClusterFirstNode.ps1 @@ -0,0 +1,124 @@ +<# + .EXAMPLE + This example shows how to install the first node in a SQL Server failover cluster. + .NOTES + This example assumes that a Failover Cluster is already present with a Cluster Name Object (CNO), IP-address. + This example also assumes that that all necessary shared disks is present, and formatted with the correct + drive letter, to accomdate the paths used during SQL Server setup. Minimum is one shared disk. + This example also assumes that the Cluster Name Object (CNO) has the permission to manage Computer Objects in + the Organizational Unit (OU) where the CNO Computer Object resides in Active Directory. This is neccessary + so that SQL Server setup can create a Virtual Computer Object (VCO) for the cluster group + (Windows Server 2012 R2 and earlier) or cluster role (Windows Server 2016 and later). Also so that the + Virtual Computer Object (VCO) can be removed when the Failover CLuster instance is uninstalled. + + See the DSC resources xFailoverCluster, xStorage and iSCSIDsc for information how to setup a failover cluster + with DSC. + + The resource is run using the SYSTEM account, but the setup is run using impersonation, with the credentials in + SetupCredential, when Action is 'InstallFailoverCluster'. + + Assumes the credentials assigned to SourceCredential have read permission on the share and on the UNC path. + The media will be copied locally, using impersonation with the credentials provided in SourceCredential, so + that the impersonated credentials in SetupCredential can access the media locally. + + Setup cannot be run using PsDscRunAsCredential at this time (see issue #405 and issue #444). That + also means that at this time PsDscRunAsCredential can not be used to access media on the UNC share. + + There is currently a bug that prevents the resource to logon to the instance if the current node is not the + active node. This is beacuse the resource tries to logon using the SYSTEM account instead of the credentials + in SetupCredential, and the resource does not currently support the built-in PsDscRunAsCredential either (see + issue #444). +#> +Configuration Example +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlInstallCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAdministratorCredential = $SqlInstallCredential, + + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAgentServiceCredential = $SqlServiceCredential + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + #region Install prerequisites for SQL Server + WindowsFeature 'NetFramework35' { + Name = 'NET-Framework-Core' + Source = '\\fileserver.company.local\images$\Win2k12R2\Sources\Sxs' # Assumes built-in Everyone has read permission to the share and path. + Ensure = 'Present' + } + + WindowsFeature 'NetFramework45' { + Name = 'NET-Framework-45-Core' + Ensure = 'Present' + } + #endregion Install prerequisites for SQL Server + + #region Install SQL Server Failover Cluster + xSQLServerSetup 'InstallNamedInstanceNode1-INST2016' + { + Action = 'InstallFailoverCluster' + ForceReboot = $false + UpdateEnabled = 'False' + SourcePath = '\\fileserver.compant.local\images$\SQL2016RTM' + SourceCredential = $SqlInstallCredential + SetupCredential = $SqlInstallCredential + + InstanceName = 'INST2016' + Features = 'SQLENGINE,AS' + + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' + InstanceDir = 'C:\Program Files\Microsoft SQL Server' + + SQLCollation = 'Finnish_Swedish_CI_AS' + SQLSvcAccount = $SqlServiceCredential + AgtSvcAccount = $SqlAgentServiceCredential + SQLSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + ASSvcAccount = $SqlServiceCredential + ASSysAdminAccounts = 'COMPANY\SQL Administrators', $SqlAdministratorCredential.UserName + + # Drive D: must be a shared disk. + InstallSQLDataDir = 'D:\MSSQL\Data' + SQLUserDBDir = 'D:\MSSQL\Data' + SQLUserDBLogDir = 'D:\MSSQL\Log' + SQLTempDBDir = 'D:\MSSQL\Temp' + SQLTempDBLogDir = 'D:\MSSQL\Temp' + SQLBackupDir = 'D:\MSSQL\Backup' + ASConfigDir = 'D:\AS\Config' + ASDataDir = 'D:\AS\Data' + ASLogDir = 'D:\AS\Log' + ASBackupDir = 'D:\AS\Backup' + ASTempDir = 'D:\AS\Temp' + + FailoverClusterNetworkName = 'TESTCLU01A' + FailoverClusterIPAddress = '192.168.0.46' + FailoverClusterGroupName = 'TESTCLU01A' + + DependsOn = '[WindowsFeature]NetFramework35','[WindowsFeature]NetFramework45' + } + #region Install SQL Server Failover Cluster + } +} diff --git a/Examples/Resources/xSQLServerSetup/5-InstallNamedInstanceInFailoverClusterSecondNode.ps1 b/Examples/Resources/xSQLServerSetup/5-InstallNamedInstanceInFailoverClusterSecondNode.ps1 new file mode 100644 index 000000000..7037fb7bb --- /dev/null +++ b/Examples/Resources/xSQLServerSetup/5-InstallNamedInstanceInFailoverClusterSecondNode.ps1 @@ -0,0 +1,98 @@ +<# + .EXAMPLE + This example shows how to add a node to an existing SQL Server failover cluster. + .NOTES + This example assumes that a Failover Cluster is already present with the first SQL Server Failover Cluster + node already installed. + This example also assumes that that the same shared disks on the first node is also present on this second + node. + + See the example 4-InstallNamedInstanceInFailoverClusterFirstNode.ps1 for information how to setup the first + SQL Server Failover Cluster node. + + The resource is run using the SYSTEM account, but the setup is run using impersonation, with the credentials in + SetupCredential, when Action is 'Addnode'. + + Assumes the credentials assigned to SourceCredential have read permission on the share and on the UNC path. + The media will be copied locally, using impersonation with the credentials provided in SourceCredential, so + that the impersonated credentials in SetupCredential can access the media locally. + + Setup cannot be run using PsDscRunAsCredential at this time (see issue #405 and issue #444). That + also means that at this time PsDscRunAsCredential can not be used to access media on the UNC share. + + There is currently a bug that prevents the resource to logon to the instance if the current node is not the + active node. This is beacuse the resource tries to logon using the SYSTEM account instead of the credentials + in SetupCredential, and the resource does not currently support the built-in PsDscRunAsCredential either (see + issue #444). +#> +Configuration Example +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlInstallCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAdministratorCredential = $SqlInstallCredential, + + [Parameter(Mandatory = $true)] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlServiceCredential, + + [Parameter()] + [ValidateNotNullorEmpty()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SqlAgentServiceCredential = $SqlServiceCredential + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + #region Install prerequisites for SQL Server + WindowsFeature 'NetFramework35' { + Name = 'NET-Framework-Core' + Source = '\\fileserver.company.local\images$\Win2k12R2\Sources\Sxs' # Assumes built-in Everyone has read permission to the share and path. + Ensure = 'Present' + } + + WindowsFeature 'NetFramework45' { + Name = 'NET-Framework-45-Core' + Ensure = 'Present' + } + #endregion Install prerequisites for SQL Server + + #region Install SQL Server Failover Cluster + xSQLServerSetup 'InstallNamedInstanceNode2-INST2016' + { + Action = 'AddNode' + ForceReboot = $false + UpdateEnabled = 'False' + SourcePath = '\\fileserver.compant.local\images$\SQL2016RTM' + SourceCredential = $SqlInstallCredential + SetupCredential = $SqlInstallCredential + + InstanceName = 'INST2016' + Features = 'SQLENGINE,AS' + + SQLSvcAccount = $SqlServiceCredential + AgtSvcAccount = $SqlAgentServiceCredential + ASSvcAccount = $SqlServiceCredential + + FailoverClusterNetworkName = 'TESTCLU01A' + + DependsOn = '[WindowsFeature]NetFramework35','[WindowsFeature]NetFramework45' + } + #region Install SQL Server Failover Cluster + } +} diff --git a/Examples/Resources/xWaitForAvailabilityGroup/1-WaitForASingleClusterGroup.ps1 b/Examples/Resources/xWaitForAvailabilityGroup/1-WaitForASingleClusterGroup.ps1 new file mode 100644 index 000000000..a249d366d --- /dev/null +++ b/Examples/Resources/xWaitForAvailabilityGroup/1-WaitForASingleClusterGroup.ps1 @@ -0,0 +1,28 @@ +<# + .EXAMPLE + This example will wait for the cluster role/group 'AGTest1'. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xWaitForAvailabilityGroup SQLConfigureAG-WaitAGTest1 + { + Name = 'AGTest1' + RetryIntervalSec = 20 + RetryCount = 30 + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/Resources/xWaitForAvailabilityGroup/2-WaitForMultipleClusterGroups.ps1 b/Examples/Resources/xWaitForAvailabilityGroup/2-WaitForMultipleClusterGroups.ps1 new file mode 100644 index 000000000..5c5e9e809 --- /dev/null +++ b/Examples/Resources/xWaitForAvailabilityGroup/2-WaitForMultipleClusterGroups.ps1 @@ -0,0 +1,37 @@ +<# + .EXAMPLE + This example will wait for both the cluster roles/groups 'AGTest1' and 'AGTest2'. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $SysAdminAccount + ) + + Import-DscResource -ModuleName xSQLServer + + node localhost + { + xWaitForAvailabilityGroup SQLConfigureAG-WaitAGTest1 + { + Name = 'AGTest1' + RetryIntervalSec = 20 + RetryCount = 30 + + PsDscRunAsCredential = $SysAdminAccount + } + + xWaitForAvailabilityGroup SQLConfigureAG-WaitAGTest2 + { + Name = 'AGTest2' + RetryIntervalSec = 20 + RetryCount = 30 + + PsDscRunAsCredential = $SysAdminAccount + } + } +} diff --git a/Examples/SQLServerNetwork.ps1 b/Examples/SQLServerNetwork.ps1 deleted file mode 100644 index 15d648fa3..000000000 --- a/Examples/SQLServerNetwork.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] -param () - -Configuration SQLNetwork -{ - Import-DscResource -Module xSQLServer - - # A Configuration expects at least one Node - Node $AllNodes.NodeName - { - # Set DCM Settings for each Node - LocalConfigurationManager - { - RebootNodeIfNeeded = $true - ConfigurationMode = "ApplyOnly" - } - - WindowsFeature "NET-Framework-Core" - { - Ensure = "Present" - Name = "NET-Framework-Core" - } - - xSqlServerSetup "RDBMS" - { - DependsOn = @("[WindowsFeature]NET-Framework-Core") - SourcePath = $Node.SourcePath - SourceFolder = $Node.SQL2012FolderPath - InstanceName = $Node.Instance - Features = $Node.Features - SetupCredential = $Node.InstallerServiceAccount - SQLCollation = "Latin1_General_CI_AS" - SQLSysAdminAccounts = $Node.AdminAccount - SQLSvcAccount = $Node.LocalSystemAccount - AgtSvcAccount = $Node.LocalSystemAccount - } - - xSqlServerFirewall "RDBMS" - { - DependsOn = @("[xSqlServerSetup]RDBMS") - SourcePath = $Node.SourcePath - SourceFolder = $Node.SQL2012FolderPath - InstanceName = $Node.Instance - Features = $Node.Features - } - - # This will enable TCP/IP protocol and set custom static port, this will also restart sql service - xSQLServerNetwork "RDBMS" - { - DependsOn = @("[xSqlServerSetup]RDBMS") - InstanceName = $Node.Instance - ProtocolName = "tcp" - IsEnabled = $true - TCPPort = 4509 - RestartService = $true - } - } -} - -#Following example of how to use credentials is intended only for demo and test purposes -#For production environments please use SSencryption, more info can be found here: -#http://blogs.msdn.com/b/powershell/archive/2014/01/31/want-to-secure-credentials-in-windows-powershell-desired-state-configuration.aspx -$InstallerServiceAccount = Get-Credential "CONTOSO\!Installer" -$LocalSystemAccount = Get-Credential "SYSTEM" - -$ConfigurationData = @{ - AllNodes = @( - #AllNodes - @{ - NodeName = "*" - PSDscAllowPlainTextPassword = $true - SourcePath = "\\FileServer\Installs" - SQL2012FolderPath = "SqlServer2012_Developer_SP3_x64" - InstallerServiceAccount = $InstallerServiceAccount - LocalSystemAccount = $LocalSystemAccount - AdminAccount = "CONTOSO\Administrator" - } - @{ - NodeName = "nodename" - Instance = "MSSQLSERVER" - Features = "SQLENGINE" - } - ) -} diff --git a/Examples/xSQLServerAvailabilityGroupListener.ps1 b/Examples/xSQLServerAvailabilityGroupListener.ps1 deleted file mode 100644 index e53f1759d..000000000 --- a/Examples/xSQLServerAvailabilityGroupListener.ps1 +++ /dev/null @@ -1,108 +0,0 @@ -$ConfigData = @{ - AllNodes = @( - @{ - NodeName= "*" - CertificateFile = "C:\Certificates\dsc-public.cer" - Thumbprint = "D6F57B6BE46A7162138687FB74DBAA1D4EB1A59B" - SqlInstanceName = "MSSQLSERVER" - PSDscAllowDomainUser = $true - }, - - @{ - NodeName = 'SQLNODE01.company.local' - Role = "PrimaryReplica" - }, - - @{ - NodeName = 'SQLNODE02.company.local' - Role = "SecondaryReplica" - } - ) -} - -Configuration SQLAlwaysOnNodeConfig -{ - param - ( - [Parameter(Mandatory=$false)] - [ValidateNotNullorEmpty()] - [PsCredential] $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName PSDesiredStateConfiguration - Import-DscResource -ModuleName xSqlServer - - Node $AllNodes.Where{$_.Role -eq "PrimaryReplica" }.NodeName - { - #region Example to add listeners - xSQLServerAvailabilityGroupListener AvailabilityGroupListenerWithSameNameAsVCO - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - AvailabilityGroup = "AG-01" - Name = "AG-01" - IpAddress = "192.168.0.73/255.255.255.0" - Port = 5301 - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerAvailabilityGroup]AvailabilityGroupForSynchronousCommitAndAutomaticFailover" - } - - xSQLServerAvailabilityGroupListener AvailabilityGroupListenerWithDifferentNameAsVCO - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - AvailabilityGroup = "AvailabilityGroup-02" - Name = "AG-02" - IpAddress = "192.168.0.74/255.255.255.0" - Port = 5302 - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerAvailabilityGroup]AvailabilityGroupForAsynchronousCommitAndManualFailover" - } - #endregion - - #region Example to remove listeners - xSQLServerAvailabilityGroupListener RemoveAvailabilityGroupListenerWithSameNameAsVCO - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - AvailabilityGroup = "AG-01" - Name = "AG-01" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerAvailabilityGroupListener]AvailabilityGroupListenerWithSameNameAsVCO" - } - - xSQLServerAvailabilityGroupListener RemoveAvailabilityGroupListenerWithDifferentNameAsVCO - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - AvailabilityGroup = "AvailabilityGroup-02" - Name = "AG-02" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerAvailabilityGroupListener]AvailabilityGroupListenerWithDifferentNameAsVCO" - } - #endregion - } - - Node $AllNodes.Where{ $_.Role -eq "SecondaryReplica" }.NodeName - { - } -} - -$SqlAdministratorCredential = Get-Credential -Message "Enter credentials for SQL Server administrator account" - -SQLAlwaysOnNodeConfig ` - -SqlAdministratorCredential $SqlAdministratorCredential ` - -ConfigurationData $ConfigData ` - -OutputPath 'C:\Configuration' diff --git a/Examples/xSQLServerConfiguration.ps1 b/Examples/xSQLServerConfiguration.ps1 deleted file mode 100644 index f3fa7263a..000000000 --- a/Examples/xSQLServerConfiguration.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -#this will configure 'show advanced options' option of default instance on local machine - -configuration SQLConfigSample -{ - Import-DscResource -ModuleName xSQLServer - Node 'localhost' - { - - LocalConfigurationManager - { - #this option should only be used during testing, remove it in production environment - DebugMode = 'ForceModuleImport' - } - - #to get all available options run sp_configure, or refer to https://msdn.microsoft.com/en-us/library/ms189631.aspx - xSQLServerConfiguration test - { - InstanceName = 'MSSQLSERVER' - OptionName = 'priority boost' - OptionValue = 1 - RestartService = $false - } - } -} - -SQLConfigSample -Set-DscLocalConfigurationManager .\SQLConfigSample -Force -Verbose #only needed if using DebugMode -Start-DscConfiguration .\SQLConfigSample -Wait -Force -Verbose diff --git a/Examples/xSQLServerEndpointPermission.ps1 b/Examples/xSQLServerEndpointPermission.ps1 deleted file mode 100644 index 05240b947..000000000 --- a/Examples/xSQLServerEndpointPermission.ps1 +++ /dev/null @@ -1,172 +0,0 @@ -$ConfigData = @{ - AllNodes = @( - @{ - NodeName= "*" - CertificateFile = "C:\Certificates\dsc-public.cer" - Thumbprint = "D6F57B6BE46A7162138687FB74DBAA1D4EB1A59B" - SqlInstanceName = "MSSQLSERVER" - PSDscAllowDomainUser = $true - }, - - @{ - NodeName = 'SQLNODE01.company.local' - Role = "PrimaryReplica" - }, - - @{ - NodeName = 'SQLNODE02.company.local' - Role = "SecondaryReplica" - } - ) -} - -Configuration SQLAlwaysOnNodeConfig -{ - param - ( - [Parameter(Mandatory=$false)] - [ValidateNotNullorEmpty()] - [PsCredential] $SqlAdministratorCredential, - - [Parameter(Mandatory=$true)] - [ValidateNotNullorEmpty()] - [PsCredential] $SqlServiceCredentialNode1, - - [Parameter(Mandatory=$true)] - [ValidateNotNullorEmpty()] - [PsCredential] $SqlServiceCredentialNode2 - ) - - Import-DscResource -ModuleName PSDesiredStateConfiguration - Import-DscResource -ModuleName xSqlServer - - Node $AllNodes.Where{$_.Role -eq "PrimaryReplica" }.NodeName - { - #region Remove endpoint permissions - xSQLServerEndpointPermission SQLConfigureEndpointPermissionPrimary - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode1.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - xSQLServerEndpointPermission SQLConfigureEndpointPermissionSecondary - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode2.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - } - #endregion - - #region Remove endpoint permissions - xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionPrimary - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode2.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionPrimary" - } - - xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionSecondary - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode2.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionSecondary" - } - #endregion - } - - Node $AllNodes.Where{ $_.Role -eq "SecondaryReplica" }.NodeName - { - xSQLServerEndpointPermission SQLConfigureEndpointPermissionPrimary - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode1.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpoint]SQLConfigureEndpoint" - } - - xSQLServerEndpointPermission SQLConfigureEndpointPermissionSecondary - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode2.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpoint]SQLConfigureEndpoint" - } - - # Remove endpoint permissions - xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionPrimary - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode2.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionPrimary" - } - - xSQLServerEndpointPermission RemoveSQLConfigureEndpointPermissionSecondary - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - Principal = $SqlServiceCredentialNode2.UserName - Permission = "CONNECT" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpointPermission]SQLConfigureEndpointPermissionSecondary" - } - } -} - -$SqlAdministratorCredential = Get-Credential -Message "Enter credentials for SQL Server administrator account" -$SqlServiceCredentialNode1 = Get-Credential -Message "Enter credentials for SQL Service account for primary replica" -$SqlServiceCredentialNode2 = Get-Credential -Message "Enter credentials for SQL Service account for secondary replica" - -SQLAlwaysOnNodeConfig ` - -SqlAdministratorCredential $SqlAdministratorCredential ` - -SqlServiceCredentialNode1 $SqlServiceCredentialNode1 ` - -SqlServiceCredentialNode2 $SqlServiceCredentialNode2 ` - -ConfigurationData $ConfigData ` - -OutputPath 'C:\Configuration' diff --git a/Examples/xSQLServerEndpointState.ps1 b/Examples/xSQLServerEndpointState.ps1 deleted file mode 100644 index 21acbb9f8..000000000 --- a/Examples/xSQLServerEndpointState.ps1 +++ /dev/null @@ -1,95 +0,0 @@ -$ConfigData = @{ - AllNodes = @( - @{ - NodeName= "*" - CertificateFile = "C:\Certificates\dsc-public.cer" - Thumbprint = "D6F57B6BE46A7162138687FB74DBAA1D4EB1A59B" - SqlInstanceName = "MSSQLSERVER" - PSDscAllowDomainUser = $true - }, - - @{ - NodeName = 'SQLNODE01.company.local' - Role = "PrimaryReplica" - }, - - @{ - NodeName = 'SQLNODE02.company.local' - Role = "SecondaryReplica" - } - ) -} - -Configuration SQLAlwaysOnNodeConfig -{ - param - ( - [Parameter(Mandatory=$false)] - [ValidateNotNullorEmpty()] - [PsCredential] $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName PSDesiredStateConfiguration - Import-DscResource -ModuleName xSqlServer - - Node $AllNodes.Where{$_.Role -eq "PrimaryReplica" }.NodeName - { - # Start the endpoint - xSQLServerEndpointState StartAlwaysOnEndpoint - { - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - State = "Started" - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - # Stop the endpoint - xSQLServerEndpointState StopAlwaysOnEndpoint - { - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - State = "Stopped" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpointState]StartAlwaysOnEndpoint" - } - } - - Node $AllNodes.Where{ $_.Role -eq "SecondaryReplica" }.NodeName - { - # Start the endpoint - xSQLServerEndpointState StartAlwaysOnEndpoint - { - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - State = "Started" - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - # Stop the endpoint - xSQLServerEndpointState StopAlwaysOnEndpoint - { - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Name = "DefaultMirrorEndpoint" - State = "Stopped" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerEndpointState]StartAlwaysOnEndpoint" - } - } -} - -$SqlAdministratorCredential = Get-Credential -Message "Enter credentials for SQL Server administrator account" - -SQLAlwaysOnNodeConfig ` - -SqlAdministratorCredential $SqlAdministratorCredential ` - -ConfigurationData $ConfigData ` - -OutputPath 'C:\Configuration' diff --git a/Examples/xSQLServerPermission.ps1 b/Examples/xSQLServerPermission.ps1 deleted file mode 100644 index 70547f0b8..000000000 --- a/Examples/xSQLServerPermission.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -$ConfigData = @{ - AllNodes = @( - @{ - NodeName= "*" - CertificateFile = "C:\Certificates\dsc-public.cer" - Thumbprint = "D6F57B6BE46A7162138687FB74DBAA1D4EB1A59B" - SqlInstanceName = "MSSQLSERVER" - PSDscAllowDomainUser = $true - }, - - @{ - NodeName = 'SQLNODE01.company.local' - Role = "PrimaryReplica" - }, - - @{ - NodeName = 'SQLNODE02.company.local' - Role = "SecondaryReplica" - } - ) -} - -Configuration SQLAlwaysOnNodeConfig -{ - param - ( - [Parameter(Mandatory=$false)] - [ValidateNotNullorEmpty()] - [PsCredential] $SqlAdministratorCredential - ) - - Import-DscResource -ModuleName PSDesiredStateConfiguration - Import-DscResource -ModuleName xSqlServer - - Node $AllNodes.Where{$_.Role -eq "PrimaryReplica" }.NodeName - { - # Add permission - xSQLServerPermission SQLConfigureAlwaysOnPermissionHealthDetectionAccount - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Principal = "NT AUTHORITY\SYSTEM" - Permission = "AlterAnyAvailabilityGroup","ViewServerState" - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - # Remove permission - xSQLServerPermission RemoveAlwaysOnPermissionHealthDetectionAccount - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Principal = "NT AUTHORITY\SYSTEM" - Permission = "AlterAnyAvailabilityGroup","ViewServerState" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerPermission]SQLConfigureAlwaysOnPermissionHealthDetectionAccount" - } - } - - Node $AllNodes.Where{ $_.Role -eq "SecondaryReplica" }.NodeName - { - # Add permission - xSQLServerPermission SQLConfigureAlwaysOnPermissionHealthDetectionAccount - { - Ensure = "Present" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Principal = "NT AUTHORITY\SYSTEM" - Permission = "AlterAnyAvailabilityGroup","ViewServerState" - - PsDscRunAsCredential = $SqlAdministratorCredential - } - - # Remove permission - xSQLServerPermission RemoveAlwaysOnPermissionHealthDetectionAccount - { - Ensure = "Absent" - NodeName = $Node.NodeName - InstanceName = $Node.SqlInstanceName - Principal = "NT AUTHORITY\SYSTEM" - Permission = "AlterAnyAvailabilityGroup","ViewServerState" - - PsDscRunAsCredential = $SqlAdministratorCredential - - DependsOn = "[xSQLServerPermission]SQLConfigureAlwaysOnPermissionHealthDetectionAccount" - } - } -} - -$SqlAdministratorCredential = Get-Credential -Message "Enter credentials for SQL Server administrator account" - -SQLAlwaysOnNodeConfig ` - -SqlAdministratorCredential $SqlAdministratorCredential ` - -ConfigurationData $ConfigData ` - -OutputPath 'C:\Configuration' diff --git a/Examples/xSQLServerReplication.ps1 b/Examples/xSQLServerReplication.ps1 deleted file mode 100644 index de07748d4..000000000 --- a/Examples/xSQLServerReplication.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -#this example should be used on local machine where two sql instances are installed -#DEFAULT instance will be configured as distributor -#PUBLISHER instance will be configured as publisher with remote distributor as default instance - -$credentials = Get-Credential 'AdminLink' -$runAsCredentials = Get-Credential - -configuration ReplicationTest -{ - Import-DscResource -ModuleName xSQLServer - - Node $AllNodes.NodeName - { - LocalConfigurationManager - { - #this option should only be used during testing, remove it in production environment - DebugMode = 'ForceModuleImport' - } - - xSQLServerReplication distributor - { - InstanceName = 'MSSQLSERVER' - AdminLinkCredentials = $credentials - DistributorMode = 'Local' - WorkingDirectory = 'C:\temp' - Ensure = 'Present' - PsDscRunAsCredential = $runAsCredentials - } - - xSQLServerReplication publisher - { - InstanceName = 'PUBLISHER' - AdminLinkCredentials = $credentials - DistributorMode = 'Remote' - WorkingDirectory = 'C:\temp' - RemoteDistributor = $Node.NodeName - Ensure = 'Present' - PsDscRunAsCredential = $runAsCredentials - } - } -} - -$ConfigurationData = @{ - AllNodes = @( - @{ - NodeName = $($env:COMPUTERNAME) - #this option should only be used during testing, remove it in production environment - PSDscAllowPlainTextPassword = $true - } - ) -} - -ReplicationTest -ConfigurationData $ConfigurationData -Set-DscLocalConfigurationManager .\ReplicationTest -Force -Verbose -Start-DscConfiguration .\ReplicationTest -Wait -Force -Verbose diff --git a/README.md b/README.md index 0b61ffa3d..960cb8e8f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,31 @@ # xSQLServer -[![Build status](https://ci.appveyor.com/api/projects/status/mxn453y284eab8li/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xsqlserver/branch/master) -[![codecov](https://codecov.io/gh/PowerShell/xSQLServer/branch/master/graph/badge.svg)](https://codecov.io/gh/PowerShell/xSQLServer>) - 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. +## Branches + +### master + +[![Build status](https://ci.appveyor.com/api/projects/status/mxn453y284eab8li/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xsqlserver/branch/master) +[![codecov](https://codecov.io/gh/PowerShell/xSQLServer/branch/master/graph/badge.svg)](https://codecov.io/gh/PowerShell/xSQLServer>) + +This is the branch containing the latest release - no contributions should be made directly to this branch. + +### dev + +[![Build status](https://ci.appveyor.com/api/projects/status/mxn453y284eab8li/branch/dev?svg=true)](https://ci.appveyor.com/project/PowerShell/xsqlserver/branch/dev) +[![codecov](https://codecov.io/gh/PowerShell/xSQLServer/branch/dev/graph/badge.svg)](https://codecov.io/gh/PowerShell/xSQLServer) + +This is the development branch to which contributions should be proposed by contributors as pull requests. This development branch will periodically be merged to the master branch, and be released to [PowerShell Gallery](https://www.powershellgallery.com/). + ## Contributing 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. +There are several ways you can contribute. You can submit an issue to report a bug. You can submit an issue to request an improvement. 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. @@ -59,41 +72,42 @@ A full list of changes in each version can be found in the [change log](CHANGELO ## Resources -* [**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 +* **[Deprecated]** ~~[**xSQLAOGroupEnsure**](#xsqlaogroupensure) resource to ensure availability group is present or absent.~~ Please use [xSQLServerAlwaysOnAvailabilityGroup](https://github.com/PowerShell/xSQLServer#xsqlserveralwaysonavailabilitygroup) and [xSQLServerAlwaysOnAvailabilityGroupReplica](https://github.com/PowerShell/xSQLServer#xsqlserveralwaysonavailabilitygroupreplica) instead. +* **[Deprecated]** ~~[**xSQLAOGroupJoin**](#xsqlaogroupjoin) resource to join a replica to an existing availability group.~~ Please use [xSQLServerAlwaysOnAvailabilityGroup](https://github.com/PowerShell/xSQLServer#xsqlserveralwaysonavailabilitygroup) and [xSQLServerAlwaysOnAvailabilityGroupReplica](https://github.com/PowerShell/xSQLServer#xsqlserveralwaysonavailabilitygroupreplica) instead. +* [**xSQLServerAlias**](#xsqlserveralias) resource to manage SQL Server client Aliases. * [**xSQLServerAlwaysOnAvailabilityGroup**](#xsqlserveralwaysonavailabilitygroup) resource to ensure an availability group is present or absent. -* [**xSQLServerAlwaysOnService**](#xsqlserveralwaysonservice) resource to enable always on on a SQL Server +* [**xSQLServerAlwaysOnAvailabilityGroupReplica**](#xsqlserveralwaysonavailabilitygroupreplica) resource to ensure an availability group replica is present or absent. +* [**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 +* [**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. +* **[Deprecated]** ~~[**xSQLServerFailoverClusterSetup**](#xsqlserverfailoverclustersetup) installs SQL Server failover cluster instances.~~ Please use [xSQLServerSetup](https://github.com/PowerShell/xSQLServer#xsqlserversetup) instead. * [**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 +* [**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 +* [**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 +* [**xSQLServerScript**](#xsqlserverscript) resource to extend DSC 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. -### xSQLAOGroupEnsure +### xSQLAOGroupEnsure **[Deprecated]** No description. -**This resource is deprecated.** The functionality of this resource has been replaced with * [**xSQLServerAlwaysOnAvailabilityGroup**](#xsqlserveralwaysonavailabilitygroup). Please do not use this resource for new development efforts. +**This resource is deprecated.** The functionality of this resource has been replaced with [**xSQLServerAlwaysOnAvailabilityGroup**](#xsqlserveralwaysonavailabilitygroup). Please do not use this resource for new deployment or development efforts. #### Requirements @@ -103,7 +117,7 @@ No description. #### Security Requirements -* 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. +* The credentials provided in the parameter `SetupCredential` must have the right **Create Computer Object** in the organizational unit (OU) in which the Cluster Name Object (CNO) resides. #### Parameters @@ -111,11 +125,11 @@ No description. * **[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[]] AvailabilityGroupSubMask** _(Write)_: Network subnet mask for listener. +* **[Uint32] 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] BackupPriority** _(Write)_: The percentage weight for backup priority (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. @@ -125,10 +139,12 @@ No description. None. -### xSQLAOGroupJoin +### xSQLAOGroupJoin **[Deprecated]** No description. +**This resource is deprecated.** The functionality of this resource has been replaced with [**xSQLServerAlwaysOnAvailabilityGroupReplica**](#xsqlserveralwaysonavailabilitygroupreplica). Please do not use this resource for new deployment or development efforts. + #### Requirements * Target machine must be running Windows Server 2008 R2 or later. @@ -157,7 +173,7 @@ No description. #### Parameters * **[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] 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. @@ -184,26 +200,63 @@ This resource is used to create, remove, and update an Always On Availability Gr #### Parameters -* **Name** _(Key)_: The name of the availability group. -* **SQLServer** _(Required)_: Hostname of the SQL Server to be configured. -* **SQLInstanceName** _(Key)_: Name of the SQL instance to be configued. -* **Ensure** _(Write)_: Specifies if the availability group should be present or absent. Default is Present. { *Present* | Absent } -* **AutomatedBackupPreference** _(Write)_: Specifies the automated backup preference for the availability group. Default is None. { Primary | SecondaryOnly | Secondary | *None* } -* **AvailabilityMode** _(Write)_: Specifies the replica availability mode. Default is 'AsynchronousCommit'. { *AsynchronousCommit* | SynchronousCommit } -* **BackupPriority** _(Write)_: Specifies the desired priority of the replicas in performing backups. The acceptable values for this parameter are: integers from 0 through 100. Of the set of replicas which are online and available, the replica that has the highest priority performs the backup. Default is 50. -* **BasicAvailabilityGroup** _(Write)_: Specifies the type of availability group is Basic. This is only available is SQL Server 2016 and later and is ignored when applied to previous versions. -* **ConnectionModeInPrimaryRole** _(Write)_: Specifies how the availability replica handles connections when in the primary role. { AllowAllConnections | AllowReadWriteConnections } -* **ConnectionModeInSecondaryRole** _(Write)_: Specifies how the availability replica handles connections when in the secondary role. { AllowNoConnections | AllowReadIntentConnectionsOnly | AllowAllConnections } -* **EndpointHostName** _(Write)_: Specifies the hostname or IP address of the availability group replica endpoint. Default is the instance network name. -* **FailureConditionLevel** _(Write)_: Specifies the automatic failover behavior of the availability group. { OnServerDown | OnServerUnresponsive | OnCriticalServerErrors | OnModerateServerErrors | OnAnyQualifiedFailureCondition } -* **FailoverMode** _(Write)_: Specifies the failover mode. Default is 'Manual'. { Automatic | *Manual* } -* **HealthCheckTimeout** _(Write)_: Specifies the length of time, in milliseconds, after which AlwaysOn availability groups declare an unresponsive server to be unhealthy. Default is 30000. +* **[String] Name** _(Key)_: The name of the availability group. +* **[String] SQLServer** _(Required)_: Hostname of the SQL Server to be configured. +* **[String] SQLInstanceName** _(Key)_: Name of the SQL instance to be configured. +* **[String] Ensure** _(Write)_: Specifies if the availability group should be present or absent. Default is Present. { *Present* | Absent } +* **[String] AutomatedBackupPreference** _(Write)_: Specifies the automated backup preference for the availability group. Default is None. { Primary | SecondaryOnly | Secondary | *None* } +* **[String] AvailabilityMode** _(Write)_: Specifies the replica availability mode. Default is 'AsynchronousCommit'. { *AsynchronousCommit* | SynchronousCommit } +* **[Uint32] BackupPriority** _(Write)_: Specifies the desired priority of the replicas in performing backups. The acceptable values for this parameter are: integers from 0 through 100. Of the set of replicas which are online and available, the replica that has the highest priority performs the backup. Default is 50. +* **[Boolean] BasicAvailabilityGroup** _(Write)_: Specifies the type of availability group is Basic. This is only available is SQL Server 2016 and later and is ignored when applied to previous versions. +* **[String] ConnectionModeInPrimaryRole** _(Write)_: Specifies how the availability replica handles connections when in the primary role. { AllowAllConnections | AllowReadWriteConnections } +* **[String] ConnectionModeInSecondaryRole** _(Write)_: Specifies how the availability replica handles connections when in the secondary role. { AllowNoConnections | AllowReadIntentConnectionsOnly | AllowAllConnections } +* **[String] EndpointHostName** _(Write)_: Specifies the hostname or IP address of the availability group replica endpoint. Default is the instance network name. +* **[String] FailureConditionLevel** _(Write)_: Specifies the automatic failover behavior of the availability group. { OnServerDown | OnServerUnresponsive | OnCriticalServerErrors | OnModerateServerErrors | OnAnyQualifiedFailureCondition } +* **[String] FailoverMode** _(Write)_: Specifies the failover mode. Default is 'Manual'. { Automatic | *Manual* } +* **[Uint32] HealthCheckTimeout** _(Write)_: Specifies the length of time, in milliseconds, after which AlwaysOn availability groups declare an unresponsive server to be unhealthy. Default is 30000. #### Examples * [Add a SQL Server Always On Availability Group](/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroup/1-CreateAvailabilityGroup.ps1) * [Remove a SQL Server Always On Availability Group](/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroup/2-RemoveAvailabilityGroup.ps1) +### xSQLServerAlwaysOnAvailabilityGroupReplica + +This resource is used to create, remove, and update an Always On Availability Group Replica. + +#### Requirements + +* Target machine must be running Windows Server 2008 R2 or later. +* Target machine must be running SQL Server Database Engine 2012 or later. +* 'NT SERVICE\ClusSvc' or 'NT AUTHORITY\SYSTEM' must have the 'Connect SQL', 'Alter Any Availability Group', and 'View Server State' permissions. + +#### Parameters + +* **[String] Name** _(Key)_: The name of the availability group replica. For named instances this must be in the following format SQLServer\InstanceName. +* **[String] AvailabilityGroupName** _(Key)_: The name of the availability group. +* **[String] SQLServer** _(Required)_: Hostname of the SQL Server to be configured. +* **[String] SQLInstanceName** _(Key)_: Name of the SQL instance to be configured. +* **[String] PrimaryReplicaSQLServer** _(Write)_: Hostname of the SQL Server where the primary replica is expected to be active. If the primary replica is not found here, the resource will attempt to find the host that holds the primary replica and connect to it. +* **[String] PrimaryReplicaSQLInstanceName** _(Write)_: Name of the SQL instance where the primary replica lives. +* **[String] Ensure** _(Write)_: Specifies if the availability group replica should be present or absent. Default is Present. { *Present* | Absent } +* **[String] AvailabilityMode** _(Write)_: Specifies the replica availability mode. Default is 'AsynchronousCommit'. { *AsynchronousCommit* | SynchronousCommit } +* **[Uint32] BackupPriority** _(Write)_: Specifies the desired priority of the replicas in performing backups. The acceptable values for this parameter are: integers from 0 through 100. Of the set of replicas which are online and available, the replica that has the highest priority performs the backup. Default is 50. +* **[String] ConnectionModeInPrimaryRole** _(Write)_: Specifies how the availability replica handles connections when in the primary role. { AllowAllConnections | AllowReadWriteConnections } +* **[String] ConnectionModeInSecondaryRole** _(Write)_: Specifies how the availability replica handles connections when in the secondary role. { AllowNoConnections | AllowReadIntentConnectionsOnly | AllowAllConnections } +* **[String] EndpointHostName** _(Write)_: Specifies the hostname or IP address of the availability group replica endpoint. Default is the instance network name. +* **[String] FailoverMode** _(Write)_: Specifies the failover mode. Default is 'Manual'. { Automatic | *Manual* } +* **[String] ReadOnlyRoutingConnectionUrl** _(Write)_: Specifies the fully-qualified domain name (FQDN) and port to use when routing to the replica for read only connections. +* **[String[]] ReadOnlyRoutingList** _(Write)_: Specifies an ordered list of replica server names that represent the probe sequence for connection director to use when redirecting read-only connections through this availability replica. This parameter applies if the availability replica is the current primary replica of the availability group. + +#### Read-Only Properties from Get-TargetResource + +* **[String] SQLServerNetName** _(Read)_: Output the NetName property from the SQL Server object. + +#### Examples + +* [Add a SQL Server Always On Availability Group Replica](/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/1-CreateAvailabilityGroupReplica.ps1) +* [Remove a SQL Server Always On Availability Group Replica](/Examples/Resources/xSQLServerAlwaysOnAvailabilityGroupReplica/2-RemoveAvailabilityGroupReplica.ps1) + ### xSQLServerAlwaysOnService No description. @@ -240,15 +293,20 @@ No description. * **[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] Ensure** _(Write)_: If the availability group listener should be present or absent. Default value is 'Present'. { *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. +* **[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 DHCP, 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. +* [Adds an Availability Group listener with the same name as the Availability Group VCO](/Examples/Resources/xSQLServerAvailabilityGroupListener/1-AddAvailabilityGroupListenerWithSameNameAsVCO.ps1) +* [Adds an Availability Group listener with a different name than the Availability Group VCO](/Examples/Resources/xSQLServerAvailabilityGroupListener/2-AddAvailabilityGroupListenerWithDifferentNameAsVCO.ps1) +* [Removes an Availability Group listener with the same name as the Availability Group VCO](/Examples/Resources/xSQLServerAvailabilityGroupListener/3-RemoveAvailabilityGroupListenerWithSameNameAsVCO.ps1) +* [Removes an Availability Group listener with a different name than the Availability Group VCO](/Examples/Resources/xSQLServerAvailabilityGroupListener/4-RemoveAvailabilityGroupListenerWithDifferentNameAsVCO.ps1) +* [Adds an Availability Group listener using DHCP on the default server subnet](/Examples/Resources/xSQLServerAvailabilityGroupListener/5-AddAvailabilityGroupListenerUsingDHCPWithDefaultServerSubnet.ps1) +* [Adds an Availability Group listener using DHCP with a specific subnet](/Examples/Resources/xSQLServerAvailabilityGroupListener/6-AddAvailabilityGroupListenerUsingDHCPWithSpecificSubnet.ps1) ### xSQLServerConfiguration @@ -270,7 +328,8 @@ No description. #### Examples -None. +* [Configure two instances on the same server to have CLR enabled](/Examples/Resources/xSQLServerConfiguration/1-ConfigureTwoInstancesOnTheSameServerToEnableClr.ps1) +* [Configure a instance to have 'Priority Boost' enabled](/Examples/Resources/xSQLServerConfiguration/2-ConfigureInstanceToEnablePriorityBoost.ps1) ### xSQLServerDatabase @@ -287,7 +346,7 @@ This resource is used to create or delete a database. For more information about #### Parameters * **[String] SQLServer** _(Key)_: The host name of the SQL Server to be configured. -* **[String] SQLInstance** _(Key)_: The name of the SQL instance to be configured. +* **[String] SQLInstanceName** _(Key)_: The name of the SQL instance to be configured. * **[String] Name** _(Key)_: The name of database to be created or dropped. * **[String] Ensure** _(Write)_: When set to 'Present', the database will be created. When set to 'Absent', the database will be dropped. { *Present* | Absent }. @@ -322,6 +381,8 @@ For more information about database owner, please read the article [Changing the 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). +>Note! When revoking permission with PermissionState 'GrantWithGrant', both the grantee and _all the other users the grantee has granted the same permission to_, will also get their permission revoked. + #### Requirements * Target machine must be running Windows Server 2008 R2 or later. @@ -329,13 +390,13 @@ For more information about permissions, please read the article [Permissions (Da #### Parameters -* **[String] Ensure** _(Write)_: If the permission should be granted (Present) or revoked (Absent). { Present | Absent }. +* **[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'. * **[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] PermissionState** _(Key)_: The state of the permission. { Grant | Deny | GrantWithGrant }. * **[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'. +* **[String] Ensure** _(Write)_: If the permission should be granted (Present) or revoked (Absent). { Present | Absent }. #### Examples @@ -367,7 +428,8 @@ Read more about recovery model in this article [View or Change the Recovery Mode ### xSQLServerDatabaseRole -No description. +This resource is used to add or remove role for a login in a database. +Read more about database role in this article [CREATE ROLE (Transact-SQL)](https://msdn.microsoft.com/en-us/library/ms187936.aspx) #### Requirements @@ -377,19 +439,24 @@ No description. #### 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] 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] 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. +* [Add Role of a database](/Examples/Resources/xSQLServerDatabaseRole/1-AddDatabaseRole.ps1) +* [Remove Role of a database](/Examples/Resources/xSQLServerDatabaseRole/2-RemoveDatabaseRole.ps1) ### xSQLServerEndpoint -No description. +This resource is used to create an endpoint. Currently it only supports creating a database mirror endpoint which can be used by, for example, AlwaysOn. + +>Note: +>The endpoint will be started after creation, but will not be enforced. Please use [**xSQLServerEndpointState**](#xsqlserverendpointstate) to make sure the endpoint remains in started state. +>To set connect permission to the endpoint, please use the resource [**xSQLServerEndpointPermission**](#xsqlserverendpointpermission). #### Requirements @@ -398,16 +465,16 @@ No description. #### Security Requirements -* 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. +* The built-in parameter PsDscRunAsCredential must be set to the credentials of an account with the permission to create and alter endpoints. #### Parameters -* **[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 +* **[String] EndpointName** _(Key)_: The name of the endpoint. +* **[String] Ensure** _(Write)_: If the endpoint should be present or absent. Default values is 'Present'. { *Present* | Absent }. +* **[Uint16] Port** _(Write)_: The network port the endpoint is listening on. Default value is 5022. +* **[String] SQLServer** _(Write)_: The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. +* **[String] SQLInstanceName** _(Key)_: The name of the SQL instance to be configured. +* **[String] IpAddress** _(Write)_: The network IP address the endpoint is listening on. Defaults to '0.0.0.0' which means listen on any valid IP address. #### Examples @@ -415,30 +482,34 @@ None. ### xSQLServerEndpointPermission -No description. +This resource is used to give connect permission to an endpoint for a user (login). #### Requirements * Target machine must be running Windows Server 2008 R2 or later. * 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] InstanceName** _(Key)_: The name of the SQL instance to be configured. +* **[String] NodeName** _(Required)_: The host name of the SQL Server to be configured. +* **[String] Ensure** _(Write)_: If the permission should be present or absent. Default value is 'Present'. { *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. +* [Add connect permission to an Endpoint](/Examples/Resources/xSQLServerEndpointPermission/1-AddConnectPermission.ps1) +* [Remove the connect permission for an Endpoint](/Examples/Resources/xSQLServerEndpointPermission/2-RemoveConnectPermission.ps1) +* [Add connect permission to both an Always On primary replica and an Always On secondary replica, and where each replica has a different SQL service account](/Examples/Resources/xSQLServerEndpointPermission/3-AddConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1) +* [Remove connect permission to both an Always On primary replica and an Always On secondary replica, and where each replica has a different SQL service account](/Examples/Resources/xSQLServerEndpointPermission/4-RemoveConnectPermissionToAlwaysOnPrimaryAndSecondaryReplicaEachWithDifferentSqlServiceAccounts.ps1) ### xSQLServerEndpointState -No description. +This resource is used to set the state of an endpoint. + +>Note: Currently this resource can only be used with Database Mirror endpoints. #### Requirements @@ -448,16 +519,17 @@ No description. #### 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 }. +* **[String] InstanceName** _(Key)_: The name of the SQL instance to be configured. +* **[String] NodeName** _(Write)_: The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. +* **[String] Name** _(Key)_: The name of the endpoint. +* **[String] State** _(Write)_: The state of the endpoint. Valid states are Started, Stopped or Disabled. Default value is 'Started'. { *Started* | Stopped | Disabled }. #### Examples -None. +* [Make sure that an endpoint is started](/Examples/Resources/xSQLServerEndpointState/1-MakeSureEndpointIsStarted.ps1) +* [Make sure that an endpoint is stopped](/Examples/Resources/xSQLServerEndpointState/2-MakeSureEndpointIsStopped.ps1) -### xSQLServerFailoverClusterSetup +### xSQLServerFailoverClusterSetup **[Deprecated]** **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. @@ -566,7 +638,7 @@ Analysis Services, SQL Browser, SQL Reporting Services and Integration Services. * **[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. +* **[String] SourceCredential** _(Write)_: Credentials used to access the path set in the parameter 'SourcePath'. This parameter 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 @@ -601,6 +673,7 @@ No description. * **[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. +* **[Boolean] Disabled** _(Write)_: Specifies if the login is disabled. Default is $false. #### Examples @@ -616,7 +689,7 @@ Read more about max degree of parallelism in this article [Configure the max deg * If the number of configured NUMA nodes configured in SQL Server equals 1, then max degree of parallelism is calculated using number of cores divided in 2 (numberOfCores / 2), then rounded up to the next integer (3.5 > 4). * If the number of cores configured in SQL Server are greater than or equal to 8 cores then max degree of parallelism will be set to 8. -* If the number of configured NUMA nodes configured in SQL Server is greater than 2 and thenumber of cores are less than 8 then max degree of parallelism will be set to the number of cores. +* If the number of configured NUMA nodes configured in SQL Server is greater than 2 and the number of cores are less than 8 then max degree of parallelism will be set to the number of cores. #### Requirements @@ -626,7 +699,7 @@ Read more about max degree of parallelism in this article [Configure the max deg #### Parameters * **[String] SQLInstance** (Key): The name of the SQL instance to be configured. -* **[String] SQLServer** _(Write)_: The host name of the SQL Server to be configured. Default value is *env:COMPUTERNAME*. +* **[String] SQLServer** _(Write)_: The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. * **[String] Ensure** _(Write)_: When set to 'Present' then max degree of parallelism will be set to either the value in parameter MaxDop or dynamically configured when parameter DynamicAlloc is set to $true. When set to 'Absent' max degree of parallelism will be set to 0 which means no limit in number of processors used in parallel plan execution. { *Present* | Absent }. * **[Boolean] DynamicAlloc** _(Write)_: If set to $true then max degree of parallelism will be dynamically configured. When this is set parameter is set to $true, the parameter MaxDop must be set to $null or not be configured. * **[Sint32] MaxDop** _(Write)_: A numeric value to limit the number of processors used in parallel plan execution. @@ -669,8 +742,8 @@ SQL Max Memory = TotalPhysicalMemory - (NumOfSQLThreads\*ThreadStackSize) - (102 #### Parameters * **[String] SQLInstance** _(Key)_: The name of the SQL instance to be configured. -* **[String] SQLServer** _(Write)_: The host name of the SQL Server to be configured. Default value is *$env:COMPUTERNAME*. -* **[Boolean] DyamicAlloc** _(Write)_: If set to $true then max memory will be dynamically configured. When this is set parameter is set to $true, the parameter MaxMemory must be set to $null or not be configured. Default value is *$false*. +* **[String] SQLServer** _(Write)_: The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. +* **[Boolean] DynamicAlloc** _(Write)_: If set to $true then max memory will be dynamically configured. When this is set parameter is set to $true, the parameter MaxMemory must be set to $null or not be configured. Default value is $false. * **[String] Ensure** _(Write)_: When set to 'Present' then min and max memory will be set to either the value in parameter MinMemory and MaxMemory or dynamically configured when parameter DynamicAlloc is set to $true. When set to 'Absent' min and max memory will be set to default values. { *Present* | Absent }. * **[Sint32] MinMemory** _(Write)_: Minimum amount of memory, in MB, in the buffer pool used by the instance of SQL Server. * **[Sint32] MaxMemory** _(Write)_: Maximum amount of memory, in MB, in the buffer pool used by the instance of SQL Server. @@ -684,7 +757,11 @@ SQL Max Memory = TotalPhysicalMemory - (NumOfSQLThreads\*ThreadStackSize) - (102 ### xSQLServerNetwork -No description. +This resource is used to change the network settings for the instance. + +Read more about the network settings in the article [TCP/IP Properties (IP Addresses Tab)](https://docs.microsoft.com/en-us/sql/tools/configuration-manager/tcp-ip-properties-ip-addresses-tab). + +>Note: Currently only TCP is supported. #### Requirements @@ -693,20 +770,25 @@ No description. #### 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. +* **[String] InstanceName** _(Key)_: The name of the SQL instance to be configured. +* **[String] ProtocolName** _(Required)_: The name of network protocol to be configured. Only tcp is currently supported. { tcp }. +* **[String] SQLServer** _(Write)_: The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. +* **[Boolean] IsEnabled** _(Write)_: Enables or disables the network protocol. +* **[String] TcpDynamicPorts** _(Write)_: Set the value to '0' if dynamic ports should be used. If static port should be used set this to a empty string value. Value can not be set to '0' if TcpPort is also set to a value. { '0','' }. +* **[String] TcpPort** _(Write)_: The TCP port(s) that SQL Server should be listening on. If the IP address should listen on more than one port, list all ports separated with a comma ('1433,1500,1501'). To use this parameter set TcpDynamicPorts to the value '' (empty string). +* **[Boolean] RestartService** _(Write)_: If set to $true then SQL Server and dependent services will be restarted if a change to the configuration is made. The default value is $false. +* **[Uint16] RestartTimeout** _(Write)_: Timeout value for restarting the SQL Server services. The default value is 120 seconds. #### Examples -None. +* [Enable TCP/IP with static port and restart SQL Server](/Examples/Resources/xSQLServerNetwork/1-EnableTcpIpWithStaticPort.ps1) +* [Enable TCP/IP with dynamic port](/Examples/Resources/xSQLServerNetwork/2-EnableTcpIpWithDynamicPort.ps1) ### xSQLServerPermission -No description. +This resource sets server permissions to a user (login). + +>Note: Currently the resource only supports ConnectSql, AlterAnyAvailabilityGroup, AlterAnyEndPoint and ViewServerState. #### Requirements @@ -716,62 +798,72 @@ No description. #### 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 }. +* **[String] InstanceName** _(Key)_: The name of the SQL instance to be configured. +* **[String] Principal** _(Key)_: The login to which permission will be set. +* **[String] Ensure** _(Write)_: If the permission should be present or absent. Default value is 'Present'. { Present | Absent }. +* **[String] NodeName** _(Write)_: The host name of the SQL Server to be configured. Default value is $env:COMPUTERNAME. +* **[String[]] Permission** _(Write)_: The permission to set for the login. Valid values are ConnectSql, AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint. { ConnectSql, AlterAnyAvailabilityGroup | AlterAnyEndPoint | ViewServerState }. #### Examples -None. +* [Add server permission for a login](/Examples/Resources/xSQLServerPermission/1-AddServerPermissionForLogin.ps1) +* [Remove server permission for a login](/Examples/Resources/xSQLServerPermission/2-RemoveServerPermissionForLogin.ps1) -### xSQLServerRole +### xSQLServerReplication No description. #### Requirements * Target machine must be running Windows Server 2008 R2 or later. -* Target machine must be running SQL Server Database Engine 2008 or later. +* Target machine must be running SQL Server 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 }. +* **[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' - Instance 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 -* [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) +* [Configure a instance as the distributor](/Examples/Resources/xSQLServerReplication/1-ConfigureInstanceAsDistributor.ps1) +* [Configure a instance as the publisher](/Examples/Resources/xSQLServerReplication/2-ConfigureInstanceAsPublisher.ps1) -### xSQLServerReplication +### xSQLServerRole -No description. +This resource is used to create a server role, when Ensure is set to 'Present'. Or remove a server role, when Ensure is set to 'Absent'. The resource also manages members in both built-in and user created server roles. For more information about server roles, please read the below articles. + +* [Create a Server Role](https://msdn.microsoft.com/en-us/library/ee677627.aspx) +* [Server-Level Roles](https://msdn.microsoft.com/en-us/library/ms188659.aspx) #### Requirements * Target machine must be running Windows Server 2008 R2 or later. -* Target machine must be running SQL Server 2008 or later. +* Target machine must be running SQL Server Database Engine 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 +* **[String] ServerRoleName** _(Key)_: The name of of SQL role to add or remove. +* **[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] Ensure** _(Write)_: An enumerated value that describes if the server role is added (Present) or dropped (Absent). Default value is 'Present'. { *Present* | Absent }. +* **[String[]] Members** _(Write)_: The members the server role should have. This parameter will replace all the current server role members with the specified members. +* **[String[]] MembersToInclude** _(Write)_: The members the server role should include. This parameter will only add members to a server role. Can not be used at the same time as parameter Members. +* **[String[]] MembersToExclude** _(Write)_: The members the server role should exclude. This parameter will only remove members from a server role. Can only be used when parameter Ensure is set to 'Present'. Can not be used at the same time as parameter Members. #### Examples -None. +* [Add server role](/Examples/Resources/xSQLServerRole/1-AddServerRole.ps1) +* [Remove server role](/Examples/Resources/xSQLServerRole/2-RemoveServerRole.ps1) +* [Add members to server role](/Examples/Resources/xSQLServerRole/3-AddMembersToServerRole.ps1) +* [Members to include in server role](/Examples/Resources/xSQLServerRole/4-MembersToIncludeInServerRole.ps1) +* [Members to exclude from server role](/Examples/Resources/xSQLServerRole/5-MembersToExcludeInServerRole.ps1) ### xSQLServerRSConfig @@ -839,9 +931,9 @@ _Note: There is a known problem running this resource using PowerShell 4.0. See * **[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. +* **[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). +* **[String[]] Variable** _(Write)_: Specifies, as a string array, a scripting variable for use in the sql 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 @@ -872,13 +964,16 @@ Installs SQL Server on the target node. * ASSysAdminAccounts * AsSvcAccount +> **Note:** It is not possible to add or remove features to a SQL Server failover cluster. This is a limitation of SQL Server. +> See article [You cannot add or remove features to a SQL Server 2008, SQL Server 2008 R2, or SQL Server 2012 failover cluster](https://support.microsoft.com/en-us/help/2547273/you-cannot-add-or-remove-features-to-a-sql-server-2008,-sql-server-2008-r2,-or-sql-server-2012-failover-cluster). + #### Parameters * **[String] Action** _(Write)_: The action to be performed. Defaults to 'Install'. *Note: AddNode is not currently functional.* { _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. +* **[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. @@ -930,27 +1025,56 @@ Installs SQL Server on the target node. #### Examples -None. +* [Install a default instance on a single server](/Examples/Resources/xSQLServerSetup/1-InstallDefaultInstanceSingleServer.ps1) +* [Install a named instance on a single server](/Examples/Resources/xSQLServerSetup/2-InstallNamedInstanceSingleServer.ps1) +* [Install a named instance on a single server from an UNC path using SourceCredential](/Examples/Resources/xSQLServerSetup/3-InstallNamedInstanceSingleServerFromUncPathUsingSourceCredential.ps1) +* [Install a named instance as the first node in SQL Server Failover Cluster](/Examples/Resources/xSQLServerSetup/4-InstallNamedInstanceInFailoverClusterFirstNode.ps1) +* [Install a named instance as the second node in SQL Server Failover Cluster](/Examples/Resources/xSQLServerSetup/5-InstallNamedInstanceInFailoverClusterSecondNode.ps1) -### xWaitforAvailabilityGroup +#### Known issues -No description. +All issues are not listed here, see [here for all issues](https://github.com/PowerShell/xSQLServer/issues). +This is known issues that severely impact the use of the resource. + +##### Failover Cluster Setup + +Setup cannot be run using PsDscRunAsCredential at this time (see issue #405 and issue #444). That +also means that at this time PsDscRunAsCredential can not be used to access media on the UNC share. + +There is currently a bug that prevents the resource to logon to the instance if the current node is not the +active node. This is because the resource tries to logon using the SYSTEM account instead of the credentials +in SetupCredential, and the resource does not currently support the built-in PsDscRunAsCredential either (see +issue #444). + +These issues are also documented in the example files [Install a named instance as the first node in SQL Server Failover Cluster](/Examples/Resources/xSQLServerSetup/4-InstallNamedInstanceInFailoverClusterFirstNode.ps1) and [Install a named instance as the second node in SQL Server Failover Cluster](/Examples/Resources/xSQLServerSetup/5-InstallNamedInstanceInFailoverClusterSecondNode.ps1). + +### xWaitForAvailabilityGroup + +This resource will wait for a cluster role/group to be created. This is used to wait for an Availability Group to create the cluster role/group in the cluster. + +>Note: This only evaluates if the cluster role/group has been created and when it found it will wait for RetryIntervalSec a last time before returning. There is currently no check to validate that the Availability Group was successfully created or that it has finished creating the Availability Group. #### Requirements * Target machine must be running Windows Server 2008 R2 or later. * Target machine must be running SQL Server Database Engine 2012 or later. +* Target machine must have access to the Failover Cluster PowerShell module. + +#### Security Requirements + +* The account running this resource must have permission in the cluster to be able to run the cmdlet Get-ClusterGroup. #### 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 +* **[String] Name** _(Key)_: Name of the cluster role/group to look for (normally the same as the Availability Group name). +* **[Uint64] RetryIntervalSec** _(Write)_: The interval, in seconds, to check for the presence of the cluster role/group. Default value is 20 seconds. When the cluster role/group has been found the resource will wait for this amount of time once more before returning. +* **[Uint32] RetryCount** _(Write)_: Maximum number of retries until the resource will timeout and throw an error. Default value is 30 times. #### Read-Only Properties from Get-TargetResource -None. +* **[Boolean] GroupExist** _(Read)_: Returns $true if the cluster role/group exist, otherwise it returns $false. Used by Get-TargetResource. #### Examples -None. +* [Wait for a cluster role/group to be available](/Examples/Resources/xWaitForAvailabilityGroup/1-WaitForASingleClusterGroup.ps1) +* [Wait for multiple cluster roles/groups to be available](/Examples/Resources/xWaitForAvailabilityGroup/2-WaitForMultipleClusterGroups.ps1) diff --git a/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroup.Tests.ps1 index 653c5f200..da7a7e86b 100644 --- a/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroup.Tests.ps1 @@ -59,6 +59,30 @@ try EndpointHostName = 'Server1' } + #region Login mocks + + $mockLogins = @{} # Will be dynamically set during tests + + $mockNtServiceClusSvcName = 'NT SERVICE\ClusSvc' + $mockNtAuthoritySystemName = 'NT AUTHORITY\SYSTEM' + + $mockAllLoginsAbsent = @{} + + $mockNtServiceClusSvcPresent = @{ + $mockNtServiceClusSvcName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtServiceClusSvcName) ) + } + + $mockNtAuthoritySystemPresent = @{ + $mockNtAuthoritySystemName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtAuthoritySystemName) ) + } + + $mockAllLoginsPresent = @{ + $mockNtServiceClusSvcName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtServiceClusSvcName) ) + $mockNtAuthoritySystemName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtAuthoritySystemName) ) + } + + #endregion + $mockConnectSqlVersion12 = { $mock = New-Object PSObject -Property @{ AvailabilityGroups = @{ @@ -97,10 +121,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -125,6 +146,8 @@ try # Type the mock as a server object $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') + $mock.Logins.GetEnumerator() | ForEach-Object { $_.Value.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Login') } + return $mock } @@ -166,10 +189,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -235,10 +255,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -305,10 +322,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -354,33 +368,6 @@ try return $mock } - $mockInvokeQueryClusterServiceCorrectPermissions = { - return New-Object PSObject -Property @{ - Tables = @{ - Rows = @{ - permission_name = @( - 'Connect SQL', - 'Alter Any Availability Group', - 'View Server State' - ) - } - } - } - } - - $mockInvokeQueryClusterServiceMissingPermissions = { - return New-Object PSObject -Property @{ - Tables = @{ - Rows = @{ - permission_name = @( - 'Connect SQL', - 'View Server State' - ) - } - } - } - } - $mockAvailabilityGroupProperty = '' # Set dynamically during runtime $mockAvailabilityGroupPropertyValue = '' # Set dynamically during runtime @@ -422,6 +409,10 @@ try Describe "xSQLServerAlwaysOnAvailabilityGroup\Get-TargetResource" { + BeforeEach { + $mockLogins = $mockAllLoginsPresent + } + Context 'When the Availability Group is Absent'{ It 'Should not return an Availability Group when Ensure is set to Present and the version is 12' { @@ -587,6 +578,10 @@ try Describe "xSQLServerAlwaysOnAvailabilityGroup\Set-TargetResource" { + BeforeEach { + $mockLogins = $mockAllLoginsPresent + } + Mock -CommandName Invoke-Query -MockWith {} -Verifiable Mock -CommandName Import-SQLPSModule -MockWith {} -Verifiable Mock -CommandName New-TerminatingError { $ErrorType } -Verifiable @@ -600,21 +595,23 @@ try It 'Should create the Availability Group when Ensure is set to Present and the SQL version is 12' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $false } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } $defaultAbsentParameters.Ensure = 'Present' { Set-TargetResource @defaultAbsentParameters } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -622,9 +619,10 @@ try It 'Should create the Availability Group when Ensure is set to Present and the SQL version is 13' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion13 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $false } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } $defaultAbsentParameters.Ensure = 'Present' $defaultAbsentParameters.BasicAvailabilityGroup = $true @@ -632,12 +630,13 @@ try { Set-TargetResource @defaultAbsentParameters } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -651,65 +650,91 @@ try Mock -CommandName Invoke-Query -MockWith {} -Verifiable Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $false } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } $defaultAbsentParameters.Ensure = 'Present' { Set-TargetResource @defaultAbsentParameters } | Should Throw 'HadrNotEnabled' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } + It 'Should throw the correct error (ClusterPermissionsMissing) when the logins "NT SERVICE\ClusSvc" or "NT AUTHORITY\SYSTEM" are absent' { + + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It + Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It + Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It + + $defaultAbsentParameters.Ensure = 'Present' + $mockLogins = $mockAllLoginsAbsent.Clone() + + { Set-TargetResource @defaultAbsentParameters } | Should Throw 'ClusterPermissionsMissing' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } + Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + It 'Should create the Availability Group when Ensure is set to Present and NT AUTHORITY\SYSTEM has the correct permissions' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceMissingPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT AUTHORITY\\SYSTEM' } -Scope It Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $false } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } $defaultAbsentParameters.Ensure = 'Present' { Set-TargetResource @defaultAbsentParameters } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT AUTHORITY\\SYSTEM' } Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } It 'Should throw the correct error, ClusterPermissionsMissing, when Ensure is set to Present, but the cluster does not have the correct permissions' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceMissingPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceMissingPermissions -Verifiable -ParameterFilter { $Query -match 'NT AUTHORITY\\SYSTEM' } -Scope It Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $false } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $false } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } $defaultAbsentParameters.Ensure = 'Present' { Set-TargetResource @defaultAbsentParameters } | Should Throw 'ClusterPermissionsMissing' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT AUTHORITY\\SYSTEM' } Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT AUTHORITY\SYSTEM' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -720,26 +745,24 @@ try AvailabilityGroups = @() Endpoints = @() IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - } + Logins = $mockNtServiceClusSvcPresent } } -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultAbsentParameters.Ensure = 'Present' { Set-TargetResource @defaultAbsentParameters } | Should Throw 'DatabaseMirroringEndpointNotFound' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -747,21 +770,21 @@ try It 'Should throw the correct error, CreateAvailabilityGroupReplicaFailed, when Ensure is set to Present, but the Availability Group Replica failed to create and the SQL version is 12' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith { throw 'CreateAvailabilityGroupReplicaFailed' } -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultAbsentParameters.Ensure = 'Present' { Set-TargetResource @defaultAbsentParameters } | Should Throw 'CreateAvailabilityGroupReplicaFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -769,21 +792,21 @@ try It 'Should throw the correct error, CreateAvailabilityGroupReplicaFailed, when Ensure is set to Present, but the Availability Group Replica failed to create and the SQL version is 13' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion13 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName New-SqlAvailabilityGroup {} -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityReplica -MockWith { throw 'CreateAvailabilityGroupReplicaFailed' } -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultAbsentParameters.Ensure = 'Present' { Set-TargetResource @defaultAbsentParameters } | Should Throw 'CreateAvailabilityGroupReplicaFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -791,9 +814,9 @@ try It 'Should throw the correct error "CreateAvailabilityGroupFailed" when Ensure is set to Present, but the Availability Group failed to create and the SQL version is 12' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityGroup { throw 'CreateAvailabilityGroupFailed' } -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Mock -CommandName Test-TargetResource -MockWith {$false} -Verifiable -Scope It $defaultAbsentParameters.Ensure = 'Present' @@ -801,12 +824,12 @@ try { Set-TargetResource @defaultAbsentParameters } | Should Throw 'CreateAvailabilityGroupFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -814,9 +837,9 @@ try It 'Should throw the correct error "CreateAvailabilityGroupFailed" when Ensure is set to Present, but the Availability Group failed to create and the SQL version is 13' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion13 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName New-SqlAvailabilityReplica -MockWith $mockNewSqlAvailabilityReplica -Verifiable -Scope It Mock -CommandName New-SqlAvailabilityGroup { throw 'CreateAvailabilityGroupFailed' } -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Mock -CommandName Test-TargetResource -MockWith {$false} -Scope It $defaultAbsentParameters.Ensure = 'Present' @@ -824,12 +847,12 @@ try { Set-TargetResource @defaultAbsentParameters } | Should Throw 'CreateAvailabilityGroupFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -846,20 +869,20 @@ try It 'Should remove the Availability Group when Ensure is set to Absent and the SQL version is 12' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith {} -Verifiable -Scope It Mock -CommandName Remove-SqlAvailabilityGroup -MockWith {} -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith {} -Verifiable -Scope It $defaultPresentParameters.Ensure = 'Absent' { Set-TargetResource @defaultPresentParameters } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -867,8 +890,8 @@ try It 'Should remove the Availability Group when Ensure is set to Absent and the SQL version is 13' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion13 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith {} -Verifiable -Scope It Mock -CommandName Remove-SqlAvailabilityGroup -MockWith {} -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith {} -Verifiable -Scope It $defaultPresentParameters.Ensure = 'Absent' @@ -881,6 +904,7 @@ try Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -913,20 +937,20 @@ try } } -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } Mock -CommandName Remove-SqlAvailabilityGroup -MockWith {} -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith {} -Verifiable -Scope It $defaultPresentParameters.Ensure = 'Absent' { Set-TargetResource @defaultPresentParameters } | Should Throw 'InstanceNotPrimaryReplica' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -934,20 +958,20 @@ try It 'Should throw the correct error message when Ensure is set to Absent but the Availability Group remove fails, and the SQL version is 12' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith {} -Verifiable -Scope It Mock -CommandName Remove-SqlAvailabilityGroup -MockWith { throw 'RemoveAvailabilityGroupFailed' } -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith {} -Verifiable -Scope It $defaultPresentParameters.Ensure = 'Absent' { Set-TargetResource @defaultPresentParameters } | Should Throw 'RemoveAvailabilityGroupFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -955,20 +979,20 @@ try It 'Should throw the correct error message when Ensure is set to Absent but the Availability Group remove fails, and the SQL version is 13' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion13 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith {} -Verifiable -Scope It Mock -CommandName Remove-SqlAvailabilityGroup -MockWith { throw 'RemoveAvailabilityGroupFailed' } -Verifiable -Scope It + Mock -CommandName Test-LoginEffectivePermissions -MockWith {} -Verifiable -Scope It $defaultPresentParameters.Ensure = 'Absent' { Set-TargetResource @defaultPresentParameters } | Should Throw 'RemoveAvailabilityGroupFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -1013,10 +1037,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -1082,10 +1103,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -1113,7 +1131,7 @@ try return $mock } -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server2' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1124,12 +1142,12 @@ try Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly -ParameterFilter { $SQLServer -eq 'Server2' } Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 0 -Exactly -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1137,7 +1155,7 @@ try It 'Should set the AutomatedBackupPreference to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1148,12 +1166,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -1161,7 +1179,7 @@ try It 'Should set the AvailabilityMode to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1172,12 +1190,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1185,7 +1203,7 @@ try It 'Should set the BackupPriority to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1196,12 +1214,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1209,7 +1227,7 @@ try It 'Should set the BasicAvailabilityGroup to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion13 -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1220,12 +1238,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -1233,7 +1251,7 @@ try It 'Should set the ConnectionModeInPrimaryRole to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1244,12 +1262,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1257,7 +1275,7 @@ try It 'Should set the ConnectionModeInSecondaryRole to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1268,12 +1286,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1318,10 +1336,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -1348,7 +1363,7 @@ try return $mock } -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1358,12 +1373,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1371,7 +1386,7 @@ try It 'Should set the EndpointUrl to the desired state when the EndpointHostName is specified' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1382,12 +1397,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1432,10 +1447,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -1462,7 +1474,7 @@ try return $mock } -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1473,12 +1485,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1523,10 +1535,7 @@ try } ) IsHadrEnabled = $true - Logins = @{ - 'NT SERVICE\ClusSvc' = @{} - 'NT AUTHORITY\SYSTEM' = @{} - } + Logins = $mockLogins Name = 'Server1' NetName = 'Server1' Roles = @{} @@ -1553,7 +1562,7 @@ try return $mock } -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1563,12 +1572,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1576,7 +1585,7 @@ try It 'Should set the FailureConditionLevel to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1587,12 +1596,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -1600,7 +1609,7 @@ try It 'Should set the FailoverMode to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It -ParameterFilter { $SQLServer -eq 'Server1' } - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1609,12 +1618,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly } @@ -1622,7 +1631,7 @@ try It 'Should set the HealthCheckTimeout to the desired state' { Mock -CommandName Connect-SQL -MockWith $mockConnectSqlVersion12 -Verifiable -Scope It - Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServiceCorrectPermissions -Verifiable -ParameterFilter { $Query -match 'NT SERVICE\\ClusSvc' } + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable -Scope It -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } $defaultPresentParametersIncorrectProperties = $defaultPresentParameters.Clone() $defaultPresentParametersIncorrectProperties.Ensure = 'Present' @@ -1633,12 +1642,12 @@ try { Set-TargetResource @defaultPresentParametersIncorrectProperties } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly - Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-SqlAvailabilityGroup -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly Assert-MockCalled -CommandName Remove-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly -ParameterFilter { $LoginName -eq 'NT SERVICE\ClusSvc' } Assert-MockCalled -CommandName Update-AvailabilityGroup -Scope It -Times 1 -Exactly Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly } @@ -1647,6 +1656,10 @@ try Describe "xSQLServerAlwaysOnAvailabilityGroup\Test-TargetResource" { + BeforeEach { + $mockLogins = $mockAllLoginsPresent + } + Context 'When the Availability Group is Absent' { It 'Should be $false when the desired state is Present and the SQL version is 12' { @@ -1828,29 +1841,6 @@ try } } } - - Describe "xSQLServerAlwaysOnAvailabilityGroup\Update-AvailabilityGroupReplica" { - Mock -CommandName New-TerminatingError { $ErrorType } -Verifiable - - Context 'When the Availability Group Replica is altered' { - It 'Should silently alter the Availability Group Replica' { - $availabilityReplica = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica - - { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should Not Throw - - Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly - } - - It 'Should throw the correct error, AlterAvailabilityGroupReplicaFailed, when altering the Availaiblity Group Replica fails' { - $availabilityReplica = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica - $availabilityReplica.Name = 'AlterFailed' - - { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should Throw 'AlterAvailabilityGroupReplicaFailed' - - Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly - } - } - } } } finally diff --git a/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.Tests.ps1 new file mode 100644 index 000000000..85b4bd7f9 --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica.Tests.ps1 @@ -0,0 +1,1279 @@ +#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 ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force +Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName 'xSQLServer' ` + -DSCResourceName 'MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica' ` + -TestType Unit + +#endregion HEADER + +function Invoke-TestSetup {} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope 'MSFT_xSQLServerAlwaysOnAvailabilityGroupReplica' { + + #region parameter mocks + + $mockSqlServer = 'Server1' + $mockSqlInstanceName = 'MSSQLSERVER' + $mockPrimaryReplicaSQLServer = 'Server2' + $mockPrimaryReplicaSQLInstanceName = 'MSSQLSERVER' + $mockAvailabilityGroupName = 'AG_AllServers' + $mockAvailabilityGroupReplicaName = $mockSqlServer + $mockEnsure = 'Present' + $mockAvailabilityMode = 'AsynchronousCommit' + $mockBackupPriority = 50 + $mockConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockEndpointHostName = $mockSqlServer + $mockFailoverMode = 'Manual' + $mockReadOnlyRoutingConnectionUrl = "TCP://$($mockSqlServer).domain.com:1433" + $mockReadOnlyRoutingList = @($mockSqlServer) + + #endregion + + #region server mock variables + + $mockServer1Name = 'Server1' + $mockServer1NetName = $mockServer1Name + $mockServer1IsHadrEnabled = $true + + $mockServer2Name = 'Server2' + $mockServer2NetName = $mockServer1Name + $mockServer2IsHadrEnabled = $true + + $mockServer3Name = 'Server3' + $mockServer3NetName = $mockServer3Name + $mockServer3IsHadrEnabled = $true + + #endregion + + #region Login mocks + + $mockLogins = @{} # Will be dynamically set during tests + + $mockNtServiceClusSvcName = 'NT SERVICE\ClusSvc' + $mockNtAuthoritySystemName = 'NT AUTHORITY\SYSTEM' + + $mockAllLoginsAbsent = @{} + + $mockNtServiceClusSvcPresent = @{ + $mockNtServiceClusSvcName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtServiceClusSvcName) ) + } + + $mockNtAuthoritySystemPresent = @{ + $mockNtAuthoritySystemName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtAuthoritySystemName) ) + } + + $mockAllLoginsPresent = @{ + $mockNtServiceClusSvcName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtServiceClusSvcName) ) + $mockNtAuthoritySystemName = ( New-Object -TypeName Microsoft.SqlServer.Management.Smo.Login($mockSqlServer,$mockNtAuthoritySystemName) ) + } + + #endregion + + #region Endpoint mocks + + $mockEndpoint = @() # Will be dynamically set during tests + + $mockEndpointPort = 5022 + + $mockDatabaseMirroringEndpointAbsent = @() + + $mockDatabaseMirroringEndpointPresent = @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'EndpointType' -Value 'DatabaseMirroring' -PassThru | + Add-Member ScriptProperty Protocol { + return @( + ( + New-Object Object | + Add-Member -MemberType ScriptProperty -Name TCP { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name ListenerPort -Value $mockendpointPort -PassThru -Force + ) + ) + } -PassThru -Force + ) + ) + } -PassThru -Force + ) + ) + + #endregion + + #region Availability Group mock variables + + $mockAvailabilityGroup1Name = 'AG_AllServers' + $mockAvailabilityGroup1PrimaryReplicaServer = $mockServer2Name + + $mockAvailabilityGroup2Name = 'AG_PrimaryOnServer2' + $mockAvailabilityGroup2PrimaryReplicaServer = $mockServer2Name + + $mockAvailabilityGroup3Name = 'AG_PrimaryOnServer3' + $mockAvailabilityGroup3PrimaryReplicaServer = $mockServer3Name + + #endregion + + #region Availability Group Replica mock variables + + $mockAlternateEndpointPort = $false + $mockAlternateEndpointProtocol = $false + + $mockAvailabilityGroupReplica1Name = $mockServer1Name + $mockAvailabilityGroupReplica1AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica1BackupPriority = 50 + $mockAvailabilityGroupReplica1ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica1ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica1EndpointProtocol = 'TCP' + $mockAvailabilityGroupReplica1EndpointPort = $mockEndpointPort + $mockAvailabilityGroupReplica1EndpointUrl = "$($mockAvailabilityGroupReplica1EndpointProtocol)://$($mockServer1Name):$($mockAvailabilityGroupReplica1EndpointPort)" + $mockAvailabilityGroupReplica1FailoverMode = 'Manual' + $mockAvailabilityGroupReplica1ReadOnlyRoutingConnectionUrl = "TCP://$($mockServer1Name).domain.com:1433" + $mockAvailabilityGroupReplica1ReadOnlyRoutingList = @($mockServer1Name) + + $mockAvailabilityGroupReplica2Name = $mockServer2Name + $mockAvailabilityGroupReplica2AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica2BackupPriority = 50 + $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica2EndpointProtocol = 'TCP' + $mockAvailabilityGroupReplica2EndpointPort = $mockEndpointPort + $mockAvailabilityGroupReplica2EndpointUrl = "$($mockAvailabilityGroupReplica2EndpointProtocol)://$($mockServer2Name):$($mockAvailabilityGroupReplica2EndpointPort)" + $mockAvailabilityGroupReplica2FailoverMode = 'Manual' + $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl = "TCP://$($mockServer2Name).domain.com:1433" + $mockAvailabilityGroupReplica2ReadOnlyRoutingList = @($mockServer2Name) + + $mockAvailabilityGroupReplica3Name = $mockServer3Name + $mockAvailabilityGroupReplica3AvailabilityMode = 'AsynchronousCommit' + $mockAvailabilityGroupReplica3BackupPriority = 50 + $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole = 'AllowAllConnections' + $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole = 'AllowNoConnections' + $mockAvailabilityGroupReplica3EndpointProtocol = 'TCP' + $mockAvailabilityGroupReplica3EndpointPort = $mockEndpointPort + $mockAvailabilityGroupReplica3EndpointUrl = "$($mockAvailabilityGroupReplica3EndpointProtocol)://$($mockServer3Name):$($mockAvailabilityGroupReplica3EndpointPort)" + $mockAvailabilityGroupReplica3FailoverMode = 'Manual' + $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl = "TCP://$($mockServer3Name).domain.com:1433" + $mockAvailabilityGroupReplica3ReadOnlyRoutingList = @($mockServer3Name) + + #endregion + + #region Function mocks + + $mockConnectSqlServer1 = { + Param + ( + [Parameter()] + [string] + $SQLServer, + + [Parameter()] + [string] + $SQLInstanceName + ) + + $mock = @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockServer1Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'NetName' -Value $mockServer1NetName -PassThru | + Add-Member -MemberType NoteProperty -Name 'IsHadrEnabled' -Value $mockServer1IsHadrEnabled -PassThru | + Add-Member ScriptProperty Logins { + return $mockLogins + } -PassThru -Force | + Add-Member ScriptProperty AvailabilityGroups { + return @{ + $mockAvailabilityGroup1Name = ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAvailabilityGroup1Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'PrimaryReplicaServerName' -Value $mockAvailabilityGroup1PrimaryReplicaServer -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalReplicaRole' -Value 'Secondary' -PassThru | + Add-Member ScriptProperty AvailabilityReplicas { + $mockAvailabilityGroupReplica1Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1Object.AvailabilityMode = $mockAvailabilityGroupReplica1AvailabilityMode + $mockAvailabilityGroupReplica1Object.BackupPriority = $mockAvailabilityGroupReplica1BackupPriority + $mockAvailabilityGroupReplica1Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica1ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica1Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica1ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica1Object.EndpointUrl = $mockAvailabilityGroupReplica1EndpointUrl + $mockAvailabilityGroupReplica1Object.FailoverMode = $mockAvailabilityGroupReplica1FailoverMode + $mockAvailabilityGroupReplica1Object.Name = $mockAvailabilityGroupReplica1Name + $mockAvailabilityGroupReplica1Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica1ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica1Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica1ReadOnlyRoutingList + + $mockAvailabilityGroupReplica2Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplica2AvailabilityMode + $mockAvailabilityGroupReplica2Object.BackupPriority = $mockAvailabilityGroupReplica2BackupPriority + $mockAvailabilityGroupReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl + $mockAvailabilityGroupReplica2Object.FailoverMode = $mockAvailabilityGroupReplica2FailoverMode + $mockAvailabilityGroupReplica2Object.Name = $mockAvailabilityGroupReplica2Name + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica2ReadOnlyRoutingList + + $mockAvailabilityGroupReplica3Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3Object.AvailabilityMode = $mockAvailabilityGroupReplica3AvailabilityMode + $mockAvailabilityGroupReplica3Object.BackupPriority = $mockAvailabilityGroupReplica3BackupPriority + $mockAvailabilityGroupReplica3Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica3Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl + $mockAvailabilityGroupReplica3Object.FailoverMode = $mockAvailabilityGroupReplica3FailoverMode + $mockAvailabilityGroupReplica3Object.Name = $mockAvailabilityGroupReplica3Name + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica3ReadOnlyRoutingList + + if ( $mockAlternateEndpointPort ) + { + $mockAvailabilityGroupReplica1Object.EndpointUrl = $mockAvailabilityGroupReplica1EndpointUrl.Replace($mockAvailabilityGroupReplica1EndpointPort,'1234') + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl.Replace($mockAvailabilityGroupReplica2EndpointPort,'1234') + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl.Replace($mockAvailabilityGroupReplica3EndpointPort,'1234') + } + + if ( $mockAlternateEndpointProtocol ) + { + $mockAvailabilityGroupReplica1Object.EndpointUrl = $mockAvailabilityGroupReplica1EndpointUrl.Replace($mockAvailabilityGroupReplica1EndpointProtocol,'UDP') + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl.Replace($mockAvailabilityGroupReplica2EndpointProtocol,'UDP') + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl.Replace($mockAvailabilityGroupReplica3EndpointProtocol,'UDP') + } + + return @{ + $mockAvailabilityGroupReplica1Name = $mockAvailabilityGroupReplica1Object + $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplica2Object + $mockAvailabilityGroupReplica3Name = $mockAvailabilityGroupReplica3Object + } + } -PassThru -Force + ) + } + } -PassThru -Force | + Add-Member ScriptProperty Endpoints { + return $mockEndpoint + } -PassThru -Force + ) + ) + + # Type the mock as a server object + $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') + + return $mock + } + + $mockConnectSqlServer2 = { + Param + ( + [Parameter()] + [string] + $SQLServer, + + [Parameter()] + [string] + $SQLInstanceName + ) + + $mock = @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockServer1Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'NetName' -Value $mockServer1NetName -PassThru | + Add-Member -MemberType NoteProperty -Name 'IsHadrEnabled' -Value $mockServer1IsHadrEnabled -PassThru | + Add-Member ScriptProperty Logins { + return $mockLogins + } -PassThru -Force | + Add-Member ScriptProperty AvailabilityGroups { + return @{ + $mockAvailabilityGroup1Name = ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAvailabilityGroup1Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'PrimaryReplicaServerName' -Value $mockAvailabilityGroup1PrimaryReplicaServer -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalReplicaRole' -Value 'Primary' -PassThru | + Add-Member ScriptProperty AvailabilityReplicas { + $mockAvailabilityGroupReplica1Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1Object.AvailabilityMode = $mockAvailabilityGroupReplica1AvailabilityMode + $mockAvailabilityGroupReplica1Object.BackupPriority = $mockAvailabilityGroupReplica1BackupPriority + $mockAvailabilityGroupReplica1Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica1ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica1Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica1ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica1Object.EndpointUrl = $mockAvailabilityGroupReplica1EndpointUrl + $mockAvailabilityGroupReplica1Object.FailoverMode = $mockAvailabilityGroupReplica1FailoverMode + $mockAvailabilityGroupReplica1Object.Name = $mockAvailabilityGroupReplica1Name + $mockAvailabilityGroupReplica1Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica1ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica1Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica1ReadOnlyRoutingList + + $mockAvailabilityGroupReplica2Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplica2AvailabilityMode + $mockAvailabilityGroupReplica2Object.BackupPriority = $mockAvailabilityGroupReplica2BackupPriority + $mockAvailabilityGroupReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl + $mockAvailabilityGroupReplica2Object.FailoverMode = $mockAvailabilityGroupReplica2FailoverMode + $mockAvailabilityGroupReplica2Object.Name = $mockAvailabilityGroupReplica2Name + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica2ReadOnlyRoutingList + + $mockAvailabilityGroupReplica3Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3Object.AvailabilityMode = $mockAvailabilityGroupReplica3AvailabilityMode + $mockAvailabilityGroupReplica3Object.BackupPriority = $mockAvailabilityGroupReplica3BackupPriority + $mockAvailabilityGroupReplica3Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica3Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl + $mockAvailabilityGroupReplica3Object.FailoverMode = $mockAvailabilityGroupReplica3FailoverMode + $mockAvailabilityGroupReplica3Object.Name = $mockAvailabilityGroupReplica3Name + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica3ReadOnlyRoutingList + + if ( $mockAlternateEndpointPort ) + { + $mockAvailabilityGroupReplica1Object.EndpointUrl = $mockAvailabilityGroupReplica1EndpointUrl.Replace($mockAvailabilityGroupReplica1EndpointPort,'1234') + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl.Replace($mockAvailabilityGroupReplica2EndpointPort,'1234') + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl.Replace($mockAvailabilityGroupReplica3EndpointPort,'1234') + } + + if ( $mockAlternateEndpointProtocol ) + { + $mockAvailabilityGroupReplica1Object.EndpointUrl = $mockAvailabilityGroupReplica1EndpointUrl.Replace($mockAvailabilityGroupReplica1EndpointProtocol,'UDP') + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl.Replace($mockAvailabilityGroupReplica2EndpointProtocol,'UDP') + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl.Replace($mockAvailabilityGroupReplica3EndpointProtocol,'UDP') + } + + return @{ + $mockAvailabilityGroupReplica1Name = $mockAvailabilityGroupReplica1Object + $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplica2Object + $mockAvailabilityGroupReplica3Name = $mockAvailabilityGroupReplica3Object + } + } -PassThru -Force + ) + $mockAvailabilityGroup2Name = ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAvailabilityGroup2Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'PrimaryReplicaServerName' -Value $mockAvailabilityGroup2PrimaryReplicaServer -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalReplicaRole' -Value 'Primary' -PassThru | + Add-Member ScriptProperty AvailabilityReplicas { + $mockAvailabilityGroupReplica2Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplica2AvailabilityMode + $mockAvailabilityGroupReplica2Object.BackupPriority = $mockAvailabilityGroupReplica2BackupPriority + $mockAvailabilityGroupReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl + $mockAvailabilityGroupReplica2Object.FailoverMode = $mockAvailabilityGroupReplica2FailoverMode + $mockAvailabilityGroupReplica2Object.Name = $mockAvailabilityGroupReplica2Name + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica2ReadOnlyRoutingList + + $mockAvailabilityGroupReplica3Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3Object.AvailabilityMode = $mockAvailabilityGroupReplica3AvailabilityMode + $mockAvailabilityGroupReplica3Object.BackupPriority = $mockAvailabilityGroupReplica3BackupPriority + $mockAvailabilityGroupReplica3Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica3Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl + $mockAvailabilityGroupReplica3Object.FailoverMode = $mockAvailabilityGroupReplica3FailoverMode + $mockAvailabilityGroupReplica3Object.Name = $mockAvailabilityGroupReplica3Name + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica3ReadOnlyRoutingList + + return @{ + $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplica2Object + $mockAvailabilityGroupReplica3Name = $mockAvailabilityGroupReplica3Object + } + } -PassThru -Force + ) + $mockAvailabilityGroup3Name = ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAvailabilityGroup3Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'PrimaryReplicaServerName' -Value $mockAvailabilityGroup3PrimaryReplicaServer -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalReplicaRole' -Value 'Secondary' -PassThru | + Add-Member ScriptProperty AvailabilityReplicas { + $mockAvailabilityGroupReplica2Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplica2AvailabilityMode + $mockAvailabilityGroupReplica2Object.BackupPriority = $mockAvailabilityGroupReplica2BackupPriority + $mockAvailabilityGroupReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl + $mockAvailabilityGroupReplica2Object.FailoverMode = $mockAvailabilityGroupReplica2FailoverMode + $mockAvailabilityGroupReplica2Object.Name = $mockAvailabilityGroupReplica2Name + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica2ReadOnlyRoutingList + + $mockAvailabilityGroupReplica3Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3Object.AvailabilityMode = $mockAvailabilityGroupReplica3AvailabilityMode + $mockAvailabilityGroupReplica3Object.BackupPriority = $mockAvailabilityGroupReplica3BackupPriority + $mockAvailabilityGroupReplica3Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica3Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl + $mockAvailabilityGroupReplica3Object.FailoverMode = $mockAvailabilityGroupReplica3FailoverMode + $mockAvailabilityGroupReplica3Object.Name = $mockAvailabilityGroupReplica3Name + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica3ReadOnlyRoutingList + + return @{ + $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplica2Object + $mockAvailabilityGroupReplica3Name = $mockAvailabilityGroupReplica3Object + } + } -PassThru -Force + ) + } + } -PassThru -Force | + Add-Member ScriptProperty Endpoints { + return $mockEndpoint + } -PassThru -Force + ) + ) + + # Type the mock as a server object + $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') + + return $mock + } + + $mockConnectSqlServer3 = { + Param + ( + [Parameter()] + [string] + $SQLServer, + + [Parameter()] + [string] + $SQLInstanceName + ) + + $mock = @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockServer1Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'NetName' -Value $mockServer1NetName -PassThru | + Add-Member -MemberType NoteProperty -Name 'IsHadrEnabled' -Value $mockServer1IsHadrEnabled -PassThru | + Add-Member ScriptProperty Logins { + return $mockLogins + } -PassThru -Force | + Add-Member ScriptProperty AvailabilityGroups { + return @{ + $mockAvailabilityGroup1Name = ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAvailabilityGroup1Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'PrimaryReplicaServerName' -Value $mockAvailabilityGroup1PrimaryReplicaServer -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalReplicaRole' -Value 'Secondary' -PassThru | + Add-Member ScriptProperty AvailabilityReplicas { + $mockAvailabilityGroupReplica1Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica1Object.AvailabilityMode = $mockAvailabilityGroupReplica1AvailabilityMode + $mockAvailabilityGroupReplica1Object.BackupPriority = $mockAvailabilityGroupReplica1BackupPriority + $mockAvailabilityGroupReplica1Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica1ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica1Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica1ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica1Object.EndpointUrl = $mockAvailabilityGroupReplica1EndpointUrl + $mockAvailabilityGroupReplica1Object.FailoverMode = $mockAvailabilityGroupReplica1FailoverMode + $mockAvailabilityGroupReplica1Object.Name = $mockAvailabilityGroupReplica1Name + $mockAvailabilityGroupReplica1Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica1ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica1Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica1ReadOnlyRoutingList + + $mockAvailabilityGroupReplica2Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplica2AvailabilityMode + $mockAvailabilityGroupReplica2Object.BackupPriority = $mockAvailabilityGroupReplica2BackupPriority + $mockAvailabilityGroupReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl + $mockAvailabilityGroupReplica2Object.FailoverMode = $mockAvailabilityGroupReplica2FailoverMode + $mockAvailabilityGroupReplica2Object.Name = $mockAvailabilityGroupReplica2Name + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica2ReadOnlyRoutingList + + $mockAvailabilityGroupReplica3Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3Object.AvailabilityMode = $mockAvailabilityGroupReplica3AvailabilityMode + $mockAvailabilityGroupReplica3Object.BackupPriority = $mockAvailabilityGroupReplica3BackupPriority + $mockAvailabilityGroupReplica3Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica3Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl + $mockAvailabilityGroupReplica3Object.FailoverMode = $mockAvailabilityGroupReplica3FailoverMode + $mockAvailabilityGroupReplica3Object.Name = $mockAvailabilityGroupReplica3Name + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica3ReadOnlyRoutingList + + return @{ + $mockAvailabilityGroupReplica1Name = $mockAvailabilityGroupReplica1Object + $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplica2Object + $mockAvailabilityGroupReplica3Name = $mockAvailabilityGroupReplica3Object + } + } -PassThru -Force + ) + $mockAvailabilityGroup2Name = ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAvailabilityGroup2Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'PrimaryReplicaServerName' -Value $mockAvailabilityGroup2PrimaryReplicaServer -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalReplicaRole' -Value 'Secondary' -PassThru | + Add-Member ScriptProperty AvailabilityReplicas { + $mockAvailabilityGroupReplica2Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplica2AvailabilityMode + $mockAvailabilityGroupReplica2Object.BackupPriority = $mockAvailabilityGroupReplica2BackupPriority + $mockAvailabilityGroupReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl + $mockAvailabilityGroupReplica2Object.FailoverMode = $mockAvailabilityGroupReplica2FailoverMode + $mockAvailabilityGroupReplica2Object.Name = $mockAvailabilityGroupReplica2Name + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica2ReadOnlyRoutingList + + $mockAvailabilityGroupReplica3Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3Object.AvailabilityMode = $mockAvailabilityGroupReplica3AvailabilityMode + $mockAvailabilityGroupReplica3Object.BackupPriority = $mockAvailabilityGroupReplica3BackupPriority + $mockAvailabilityGroupReplica3Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica3Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl + $mockAvailabilityGroupReplica3Object.FailoverMode = $mockAvailabilityGroupReplica3FailoverMode + $mockAvailabilityGroupReplica3Object.Name = $mockAvailabilityGroupReplica3Name + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica3ReadOnlyRoutingList + + return @{ + $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplica2Object + $mockAvailabilityGroupReplica3Name = $mockAvailabilityGroupReplica3Object + } + } -PassThru -Force + ) + $mockAvailabilityGroup3Name = ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAvailabilityGroup3Name -PassThru | + Add-Member -MemberType NoteProperty -Name 'PrimaryReplicaServerName' -Value $mockAvailabilityGroup3PrimaryReplicaServer -PassThru | + Add-Member -MemberType NoteProperty -Name 'LocalReplicaRole' -Value 'Primary' -PassThru | + Add-Member ScriptProperty AvailabilityReplicas { + $mockAvailabilityGroupReplica2Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica2Object.AvailabilityMode = $mockAvailabilityGroupReplica2AvailabilityMode + $mockAvailabilityGroupReplica2Object.BackupPriority = $mockAvailabilityGroupReplica2BackupPriority + $mockAvailabilityGroupReplica2Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica2ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica2Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica2ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica2Object.EndpointUrl = $mockAvailabilityGroupReplica2EndpointUrl + $mockAvailabilityGroupReplica2Object.FailoverMode = $mockAvailabilityGroupReplica2FailoverMode + $mockAvailabilityGroupReplica2Object.Name = $mockAvailabilityGroupReplica2Name + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica2ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica2Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica2ReadOnlyRoutingList + + $mockAvailabilityGroupReplica3Object = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $mockAvailabilityGroupReplica3Object.AvailabilityMode = $mockAvailabilityGroupReplica3AvailabilityMode + $mockAvailabilityGroupReplica3Object.BackupPriority = $mockAvailabilityGroupReplica3BackupPriority + $mockAvailabilityGroupReplica3Object.ConnectionModeInPrimaryRole = $mockAvailabilityGroupReplica3ConnectionModeInPrimaryRole + $mockAvailabilityGroupReplica3Object.ConnectionModeInSecondaryRole = $mockAvailabilityGroupReplica3ConnectionModeInSecondaryRole + $mockAvailabilityGroupReplica3Object.EndpointUrl = $mockAvailabilityGroupReplica3EndpointUrl + $mockAvailabilityGroupReplica3Object.FailoverMode = $mockAvailabilityGroupReplica3FailoverMode + $mockAvailabilityGroupReplica3Object.Name = $mockAvailabilityGroupReplica3Name + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingConnectionUrl = $mockAvailabilityGroupReplica3ReadOnlyRoutingConnectionUrl + $mockAvailabilityGroupReplica3Object.ReadOnlyRoutingList = $mockAvailabilityGroupReplica3ReadOnlyRoutingList + + return @{ + $mockAvailabilityGroupReplica2Name = $mockAvailabilityGroupReplica2Object + $mockAvailabilityGroupReplica3Name = $mockAvailabilityGroupReplica3Object + } + } -PassThru -Force + ) + } + } -PassThru -Force | + Add-Member ScriptProperty Endpoints { + return $mockEndpoint + } -PassThru -Force + ) + ) + + # Type the mock as a server object + $mock.PSObject.TypeNames.Insert(0,'Microsoft.SqlServer.Management.Smo.Server') + + return $mock + } + + $mockAvailabilityGroupReplicaPropertyName = '' # Set dynamically during runtime + $mockAvailabilityGroupReplicaPropertyValue = '' # Set dynamically during runtime + + $mockUpdateAvailabilityGroupReplica = { + Param + ( + [Parameter()] + [Microsoft.SqlServer.Management.Smo.AvailabilityReplica] + $AvailabilityGroupReplica + ) + + if ( [string]::IsNullOrEmpty($mockAvailabilityGroupReplicaPropertyName) -and [string]::IsNullOrEmpty($mockAvailabilityGroupReplicaPropertyValue) ) + { + return + } + + if ( ( $mockAvailabilityGroupReplicaPropertyValue -join ',' ) -ne ( $AvailabilityGroupReplica.$mockAvailabilityGroupReplicaPropertyName -join ',' ) ) + { + throw + } + } + + #endregion + + Describe 'xSQLServerAlwaysOnAvailabilityGroupReplica\Get-TargetResource' { + BeforeEach { + $getTargetResourceParameters = @{ + Name = $mockAvailabilityGroupReplicaName + AvailabilityGroupName = $mockAvailabilityGroupName + SQLServer = $mockSqlServer + SQLInstanceName = $mockSqlInstanceName + } + + $mockEndpoint = $mockDatabaseMirroringEndpointPresent + + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer1 -Verifiable + } + + Context 'When the Availability Group Replica is absent' { + + It 'Should not return an Availability Group Replica' { + + $getTargetResourceParameters.Name = 'AbsentReplica' + $getTargetResourceParameters.AvailabilityGroupName = 'AbsentAG' + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.AvailabilityGroupName | Should BeNullOrEmpty + $getTargetResourceResult.AvailabilityMode | Should BeNullOrEmpty + $getTargetResourceResult.BackupPriority | Should BeNullOrEmpty + $getTargetResourceResult.ConnectionModeInPrimaryRole | Should BeNullOrEmpty + $getTargetResourceResult.ConnectionModeInSecondaryRole | Should BeNullOrEmpty + $getTargetResourceResult.EndpointUrl | Should BeNullOrEmpty + $getTargetResourceResult.EndpointPort | Should Be $mockendpointPort + $getTargetResourceResult.Ensure | Should Be 'Absent' + $getTargetResourceResult.FailoverMode | Should BeNullOrEmpty + $getTargetResourceResult.Name | Should BeNullOrEmpty + $getTargetResourceResult.ReadOnlyRoutingConnectionUrl | Should BeNullOrEmpty + $getTargetResourceResult.ReadOnlyRoutingList | Should BeNullOrEmpty + $getTargetResourceResult.SQLServer | Should Be $mockSqlServer + $getTargetResourceResult.SQLInstanceName | Should Be $mockSqlInstanceName + $getTargetResourceResult.SQLServerNetName | Should Be $mockSqlServer + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + } + + Context 'When the Availability Group Replica is present' { + + $mockEnsure = 'Present' + + It 'Should return an Availability Group Replica' { + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.AvailabilityGroupName | Should Be $mockAvailabilityGroupName + $getTargetResourceResult.AvailabilityMode | Should Be $mockAvailabilityMode + $getTargetResourceResult.BackupPriority | Should Be $mockBackupPriority + $getTargetResourceResult.ConnectionModeInPrimaryRole | Should Be $mockConnectionModeInPrimaryRole + $getTargetResourceResult.ConnectionModeInSecondaryRole | Should Be $mockConnectionModeInSecondaryRole + $getTargetResourceResult.EndpointUrl | Should Be $mockAvailabilityGroupReplica1EndpointUrl + $getTargetResourceResult.EndpointPort | Should Be $mockendpointPort + $getTargetResourceResult.Ensure | Should Be $mockEnsure + $getTargetResourceResult.FailoverMode | Should Be $mockFailoverMode + $getTargetResourceResult.Name | Should Be $mockSqlServer + $getTargetResourceResult.ReadOnlyRoutingConnectionUrl | Should Be $mockReadOnlyRoutingConnectionUrl + $getTargetResourceResult.ReadOnlyRoutingList | Should Be $mockSqlServer + $getTargetResourceResult.SQLServer | Should Be $mockSqlServer + $getTargetResourceResult.SQLInstanceName | Should Be $mockSqlInstanceName + $getTargetResourceResult.SQLServerNetName | Should Be $mockSqlServer + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + } + } + + Describe 'xSQLServerAlwaysOnAvailabilityGroupReplica\Set-TargetResource' { + + BeforeAll { + Mock -CommandName Import-SQLPSModule -MockWith {} -Verifiable + Mock -CommandName New-TerminatingError { $ErrorType } -Verifiable + } + + BeforeEach { + $mockEndpoint = $mockDatabaseMirroringEndpointPresent + $mockLogins = $mockNtServiceClusSvcPresent + $mockServer1IsHadrEnabled = $true + + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer1 -Verifiable -ParameterFilter { $SQLServer -eq $mockServer1Name } + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer2 -Verifiable -ParameterFilter { $SQLServer -eq $mockServer2Name } + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer3 -Verifiable -ParameterFilter { $SQLServer -eq $mockServer3Name } + Mock -CommandName Join-SqlAvailabilityGroup -MockWith {} -Verifiable + Mock -CommandName New-SqlAvailabilityReplica {} -Verifiable + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $true } -Verifiable + } + + Context 'When the desired state is absent' { + + BeforeAll { + Mock -CommandName Update-AvailabilityGroupReplica {} -Verifiable + } + + BeforeEach { + $setTargetResourceParameters = @{ + Name = $mockSqlServer + AvailabilityGroupName = $mockAvailabilityGroupName + SQLServer = $mockSqlServer + SQLInstanceName = $mockSqlInstanceName + Ensure = 'Absent' + } + } + + It 'Should silently remove the availability group replica' { + + Mock -CommandName Remove-SqlAvailabilityReplica -MockWith {} -Verifiable + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error (RemoveAvailabilityGroupReplicaFailed) when removing the availability group replica fails' { + + Mock -CommandName Remove-SqlAvailabilityReplica -MockWith { Throw 'RemoveAvailabilityGroupReplicaFailed' } -Verifiable + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'RemoveAvailabilityGroupReplicaFailed' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + } + + Context 'When the desired state is present and the availability group is absent' { + + BeforeAll { + Mock -CommandName Remove-SqlAvailabilityReplica -MockWith {} -Verifiable + Mock -CommandName Update-AvailabilityGroupReplica {} -Verifiable + } + + BeforeEach { + $setTargetResourceParameters = @{ + Name = $mockSqlServer + AvailabilityGroupName = $mockAvailabilityGroup2Name + SQLServer = $mockSqlServer + SQLInstanceName = $mockSqlInstanceName + PrimaryReplicaSQLServer = $mockPrimaryReplicaSQLServer + PrimaryReplicaSQLInstanceName = $mockPrimaryReplicaSQLInstanceName + Ensure = $mockEnsure + AvailabilityMode = $mockAvailabilityMode + BackupPriority = $mockBackupPriority + ConnectionModeInPrimaryRole = $mockConnectionModeInPrimaryRole + ConnectionModeInSecondaryRole = $mockConnectionModeInSecondaryRole + EndpointHostName = $mockEndpointHostName + FailoverMode = $mockFailoverMode + ReadOnlyRoutingConnectionUrl = $mockReadOnlyRoutingConnectionUrl + ReadOnlyRoutingList = $mockReadOnlyRoutingList + } + } + + It 'Should throw the correct error (HadrNotEnabled) when HADR is not enabled' { + + $mockServer1IsHadrEnabled = $false + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'HadrNotEnabled' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error (ClusterPermissionsMissing) when the logins "NT SERVICE\ClusSvc" or "NT AUTHORITY\SYSTEM" are absent' { + + $mockLogins = $mockAllLoginsAbsent.Clone() + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'ClusterPermissionsMissing' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error (ClusterPermissionsMissing) when the logins "NT SERVICE\ClusSvc" and "NT AUTHORITY\SYSTEM" do not have permissions to manage availability groups' { + + $mockLogins = $mockAllLoginsPresent.Clone() + + Mock -CommandName Test-LoginEffectivePermissions -MockWith { $false } -Verifiable + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'ClusterPermissionsMissing' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 2 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should create the availability group replica when "NT SERVICE\ClusSvc" is present and has the permissions to manage availability groups' { + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should create the availability group replica when "NT AUTHORITY\SYSTEM" is present and has the permissions to manage availability groups' { + + $mockLogins = $mockNtAuthoritySystemPresent + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error (DatabaseMirroringEndpointNotFound) when the database mirroring endpoint is not absent' { + + $mockEndpoint = $mockDatabaseMirroringEndpointAbsent + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'DatabaseMirroringEndpointNotFound' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should create the availability group replica when the endpoint hostname is not defined' { + + $setTargetResourceParameters.EndpointHostName = '' + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should create the availability group replica when primary replica server is incorrectly supplied and the availability group exists' { + + $setTargetResourceParameters.PrimaryReplicaSQLServer = $mockServer3Name + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error (CreateAvailabilityGroupReplicaFailed) when the availability group replica fails to create' { + + Mock -CommandName New-SqlAvailabilityReplica { throw } -Verifiable + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'CreateAvailabilityGroupReplicaFailed' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error (JoinAvailabilityGroupFailed) when the availability group replica fails to join the availability group' { + + Mock -CommandName Join-SqlAvailabilityGroup -MockWith { throw } -Verifiable + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'JoinAvailabilityGroupFailed' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error (AvailabilityGroupNotFound) when the availability group does not exist on the primary replica' { + + $setTargetResourceParameters.AvailabilityGroupName = 'DoesNotExist' + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'AvailabilityGroupNotFound' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + } + + Context 'When the desired state is present and the availability group is present' { + + BeforeAll { + Mock -CommandName Remove-SqlAvailabilityReplica -MockWith {} -Verifiable + Mock -CommandName Update-AvailabilityGroupReplica -MockWith $mockUpdateAvailabilityGroupReplica -Verifiable + + # Create a hash table to provide test properties and values for the update tests + $mockTestProperties = @{ + AvailabilityMode = 'SynchronousCommit' + BackupPriority = 75 + ConnectionModeInPrimaryRole = 'AllowReadWriteConnections' + ConnectionModeInSecondaryRole = 'AllowReadIntentConnectionsOnly' + FailoverMode = 'Automatic' + ReadOnlyRoutingConnectionUrl = 'TCP://TestHost.domain.com:1433' + ReadOnlyRoutingList = @('Server1','Server2') + } + } + + BeforeEach { + $mockAlternateEndpointPort = $false + $mockAlternateEndpointProtocol = $false + + $setTargetResourceParameters = @{ + Name = $mockSqlServer + AvailabilityGroupName = $mockAvailabilityGroupName + SQLServer = $mockSqlServer + SQLInstanceName = $mockSqlInstanceName + PrimaryReplicaSQLServer = $mockPrimaryReplicaSQLServer + PrimaryReplicaSQLInstanceName = $mockPrimaryReplicaSQLInstanceName + Ensure = $mockEnsure + AvailabilityMode = $mockAvailabilityMode + BackupPriority = $mockBackupPriority + ConnectionModeInPrimaryRole = $mockConnectionModeInPrimaryRole + ConnectionModeInSecondaryRole = $mockConnectionModeInSecondaryRole + EndpointHostName = $mockEndpointHostName + FailoverMode = $mockFailoverMode + ReadOnlyRoutingConnectionUrl = $mockReadOnlyRoutingConnectionUrl + ReadOnlyRoutingList = $mockReadOnlyRoutingList + } + } + + It 'Should throw the correct error (ReplicaNotFound) when the availability group replica does not exist' { + + $setTargetResourceParameters.Name = 'ReplicaNotFound' + + { Set-TargetResource @setTargetResourceParameters } | Should Throw 'ReplicaNotFound' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 0 -Exactly + } + + foreach ( $mockTestProperty in $mockTestProperties.GetEnumerator() ) + { + It "Should set the property '$($mockTestProperty.Key)' to the desired state" { + + $mockAvailabilityGroupReplicaPropertyName = $mockTestProperty.Key + $mockAvailabilityGroupReplicaPropertyValue = $mockTestProperty.Value + $setTargetResourceParameters.$mockAvailabilityGroupReplicaPropertyName = $mockAvailabilityGroupReplicaPropertyValue + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 #-Exactly + } + } + + It "Should set the Endpoint Hostname to the desired state" { + + $setTargetResourceParameters.EndpointHostName = 'AnotherEndpointHostName' + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly + } + + It "Should set the Endpoint Port to the desired state" { + + $mockAvailabilityGroupReplicaPropertyName = 'EndpointUrl' + $mockAvailabilityGroupReplicaPropertyValue = $mockAvailabilityGroupReplica1EndpointUrl + $mockAlternateEndpointPort = $true + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly + } + + It "Should set the Endpoint Protocol to the desired state" { + + $mockAvailabilityGroupReplicaPropertyName = 'EndpointUrl' + $mockAvailabilityGroupReplicaPropertyValue = $mockAvailabilityGroupReplica1EndpointUrl + $mockAlternateEndpointProtocol = $true + + { Set-TargetResource @setTargetResourceParameters } | Should Not Throw + + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer1Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer2Name } -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -ParameterFilter { $SQLServer -eq $mockServer3Name } -Times 0 -Exactly + Assert-MockCalled -CommandName Import-SQLPSModule -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Join-SqlAvailabilityGroup -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SqlAvailabilityReplica -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Test-LoginEffectivePermissions -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-AvailabilityGroupReplica -Scope It -Times 1 -Exactly + } + } + } + + Describe 'xSQLServerAlwaysOnAvailabilityGroupReplica\Test-TargetResource' { + + BeforeEach { + $mockAlternateEndpointPort = $false + $mockAlternateEndpointProtocol = $false + $mockEndpoint = $mockDatabaseMirroringEndpointPresent + + $testTargetResourceParameters = @{ + Name = $mockSqlServer + AvailabilityGroupName = $mockAvailabilityGroupName + SQLServer = $mockSqlServer + SQLInstanceName = $mockSqlInstanceName + PrimaryReplicaSQLServer = $mockPrimaryReplicaSQLServer + PrimaryReplicaSQLInstanceName = $mockPrimaryReplicaSQLInstanceName + Ensure = $mockEnsure + AvailabilityMode = $mockAvailabilityMode + BackupPriority = $mockBackupPriority + ConnectionModeInPrimaryRole = $mockConnectionModeInPrimaryRole + ConnectionModeInSecondaryRole = $mockConnectionModeInSecondaryRole + EndpointHostName = $mockEndpointHostName + FailoverMode = $mockFailoverMode + ReadOnlyRoutingConnectionUrl = $mockReadOnlyRoutingConnectionUrl + ReadOnlyRoutingList = $mockReadOnlyRoutingList + } + + Mock -CommandName Connect-SQL -MockWith $mockConnectSqlServer1 -Verifiable + } + + Context 'When the desired state is absent' { + + It 'Should return $true when the Availability Replica is absent' { + + $testTargetResourceParameters.Name = $mockAvailabilityGroupReplica2Name + $testTargetResourceParameters.AvailabilityGroupName = $mockAvailabilityGroup2Name + $testTargetResourceParameters.Ensure = 'Absent' + + Test-TargetResource @testTargetResourceParameters | Should Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the Availability Replica is present' { + + $testTargetResourceParameters.Ensure = 'Absent' + + Test-TargetResource @testTargetResourceParameters | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + } + + Context 'When the desired state is present' { + + BeforeAll { + $propertiesToCheck = @{ + AvailabilityMode = 'SynchronousCommit' + BackupPriority = 42 + ConnectionModeInPrimaryRole = 'AllowReadWriteConnections' + ConnectionModeInSecondaryRole = 'AllowReadIntentConnectionsOnly' + FailoverMode = 'Automatic' + ReadOnlyRoutingConnectionUrl = 'WrongUrl' + ReadOnlyRoutingList = @('WrongServer') + } + } + + It 'Should return $false when the Availability Replica is absent' { + + $testTargetResourceParameters.Name = $mockAvailabilityGroupReplica2Name + $testTargetResourceParameters.AvailabilityGroupName = $mockAvailabilityGroup2Name + + Test-TargetResource @testTargetResourceParameters | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $true when the Availability Replica is present' { + + Test-TargetResource @testTargetResourceParameters | Should Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + foreach ( $propertyToCheck in $propertiesToCheck.GetEnumerator() ) + { + It "Should return $false when the Availability Replica is present and the property '$($propertyToCheck.Key)' is not in the desired state" { + $testTargetResourceParameters.($propertyToCheck.Key) = $propertyToCheck.Value + + Test-TargetResource @testTargetResourceParameters | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + } + + It 'Should return $false when the Availability Replica is present and the Availabiltiy Mode is not in the desired state' { + + $testTargetResourceParameters.AvailabilityMode = 'SynchronousCommit' + + Test-TargetResource @testTargetResourceParameters | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $true when the Availability Replica is present and the Endpoint Hostname is not specified' { + + $testTargetResourceParameters.EndpointHostName = '' + + Test-TargetResource @testTargetResourceParameters | Should Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the Availability Replica is present and the Endpoint Hostname is not in the desired state' { + + $testTargetResourceParameters.EndpointHostName = 'OtherHostName' + + Test-TargetResource @testTargetResourceParameters | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the Availability Replica is present and the Endpoint Protocol is not in the desired state' { + + $mockAlternateEndpointProtocol = $true + + Test-TargetResource @testTargetResourceParameters | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the Availability Replica is present and the Endpoint Port is not in the desired state' { + + $mockAlternateEndpointPort = $true + + Test-TargetResource @testTargetResourceParameters | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/MSFT_xSQLServerAlwaysOnService.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerAlwaysOnService.Tests.ps1 index 764d06a4b..b6ff9a12e 100644 --- a/Tests/Unit/MSFT_xSQLServerAlwaysOnService.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerAlwaysOnService.Tests.ps1 @@ -1,7 +1,7 @@ $script:DSCModuleName = 'xSQLServer' $script:DSCResourceName = 'MSFT_xSQLServerAlwaysOnService' -# Unit Test Template Version: 1.1.0 +# 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'))) ) @@ -13,7 +13,7 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit $disableHadr = @{ Ensure = 'Absent' @@ -47,26 +47,26 @@ try Context 'When HADR is disabled' { Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $false } } -ModuleName $script:DSCResourceName -Verifiable It 'Should return that HADR is disabled' { - + # Get the current state $result = Get-TargetResource @enableHadr - + $result.IsHadrEnabled | Should Be $false Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } It 'Should return that HADR is disabled' { - + # Get the current state $result = Get-TargetResource @enableHadrNamedInstance - + $result.IsHadrEnabled | Should Be $false Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly @@ -76,31 +76,59 @@ try Context 'When HADR is enabled' { Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $true } } -ModuleName $script:DSCResourceName -Verifiable It 'Should return that HADR is enabled' { - + # Get the current state $result = Get-TargetResource @enableHadr - + $result.IsHadrEnabled | Should Be $true Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } It 'Should return that HADR is enabled' { - + # Get the current state $result = Get-TargetResource @enableHadrNamedInstance - + $result.IsHadrEnabled | Should Be $true Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly } } + + # This it regression test for issue #519. + Context 'When Server.IsHadrEnabled returns $null' { + Mock -CommandName Connect-SQL -MockWith { + return New-Object PSObject -Property @{ + IsHadrEnabled = $null + } + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should fail with the correct error message' { + { Get-TargetResource @enableHadr } | Should Not Throw 'Index operation failed; the array index evaluated to null' + { Get-TargetResource @enableHadr } | Should Throw 'The status of property Server.IsHadrEnabled was netiher $true or $false. Status is ''''. InnerException: Server.IsHadrEnabled was set to $null.' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 2 -Exactly + } + } + + Context 'When Server.IsHadrEnabled returns unexpected value' { + Mock -CommandName Connect-SQL -MockWith { + return New-Object PSObject -Property @{ + IsHadrEnabled = 'UnknownStatus' + } + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should fail with the correct error message' { + { Get-TargetResource @enableHadr } | Should Throw 'The status of property Server.IsHadrEnabled was netiher $true or $false. Status is ''UnknownStatus''. InnerException: Server.IsHadrEnabled was set to unexpected value.' + Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + } } Describe "$($script:DSCResourceName)\Set-TargetResource" { @@ -113,23 +141,23 @@ try Mock -CommandName Enable-SqlAlwaysOn -MockWith {} -ModuleName $script:DSCResourceName Mock -CommandName Import-SQLPSModule -MockWith {} -ModuleName $script:DSCResourceName - + Mock -CommandName New-TerminatingError { $ErrorType } -ModuleName $script:DSCResourceName Mock -CommandName New-VerboseMessage -MockWith {} -ModuleName $script:DSCResourceName - + Mock -CommandName Restart-SqlService -MockWith {} -ModuleName $script:DSCResourceName -Verifiable Context 'When HADR is not in the desired state' { It 'Should enable SQL Always On when Ensure is set to Present' { - + Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $true } } -ModuleName $script:DSCResourceName -Verifiable - + Set-TargetResource @enableHadr Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Disable-SqlAlwaysOn -Scope It -Times 0 @@ -138,13 +166,13 @@ try } It 'Should disable SQL Always On when Ensure is set to Absent' { - + Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $false } } -ModuleName $script:DSCResourceName -Verifiable - + Set-TargetResource @disableHadr Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Disable-SqlAlwaysOn -Scope It -Times 1 @@ -153,13 +181,13 @@ try } It 'Should enable SQL Always On on a named instance when Ensure is set to Present' { - + Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $true } } -ModuleName $script:DSCResourceName -Verifiable - + Set-TargetResource @enableHadrNamedInstance Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Disable-SqlAlwaysOn -Scope It -Times 0 @@ -168,13 +196,13 @@ try } It 'Should disable SQL Always On on a named instance when Ensure is set to Absent' { - + Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $false } } -ModuleName $script:DSCResourceName -Verifiable - + Set-TargetResource @disableHadrNamedInstance Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Disable-SqlAlwaysOn -Scope It -Times 1 @@ -183,13 +211,13 @@ try } It 'Should throw the correct error message when Ensure is set to Present, but IsHadrEnabled is $false' { - + Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $false } } -ModuleName $script:DSCResourceName -Verifiable - + { Set-TargetResource @enableHadr } | Should Throw 'AlterAlwaysOnServiceFailed' Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Disable-SqlAlwaysOn -Scope It -Times 0 @@ -199,13 +227,13 @@ try } It 'Should throw the correct error message when Ensure is set to Absent, but IsHadrEnabled is $true' { - + Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ + return New-Object PSObject -Property @{ IsHadrEnabled = $true } } -ModuleName $script:DSCResourceName -Verifiable - + { Set-TargetResource @disableHadr } | Should Throw 'AlterAlwaysOnServiceFailed' Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Disable-SqlAlwaysOn -Scope It -Times 1 @@ -217,19 +245,28 @@ try } Describe "$($script:DSCResourceName)\Test-TargetResource" { - - Mock -CommandName Connect-SQL -MockWith { - return New-Object PSObject -Property @{ - IsHadrEnabled = $true + Context 'When HADR is not in the desired state' { + Mock -CommandName Connect-SQL -MockWith { + return New-Object PSObject -Property @{ + IsHadrEnabled = $true + } + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should cause Test-TargetResource to return false when not in the desired state' { + Test-TargetResource @disableHadr | Should be $false } - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should cause Test-TargetResource to return false when not in the desired state' { - Test-TargetResource @disableHadr | Should be $false } - It 'Should cause Test-TargetResource to return true when in the desired state' { - Test-TargetResource @enableHadr | Should be $true + Context 'When HADR is in the desired state' { + Mock -CommandName Connect-SQL -MockWith { + return New-Object PSObject -Property @{ + IsHadrEnabled = $true + } + } -ModuleName $script:DSCResourceName -Verifiable + + It 'Should cause Test-TargetResource to return true when in the desired state' { + Test-TargetResource @enableHadr | Should be $true + } } } } diff --git a/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 index ef96a7cf3..a38c01702 100644 --- a/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerAvailabilityGroupListener.Tests.ps1 @@ -3,10 +3,10 @@ $script:DSCResourceName = 'MSFT_xSQLServerAvailabilityGroupListener' #region HEADER -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +# 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'))) ) + (-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\')) } @@ -16,669 +16,852 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -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') - +function Invoke-TestSetup { # 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 - } + Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global +} - It 'Should return that DHCP is not used' { - $result.DHCP | Should Be $false - } +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} - It 'Should call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + + $mockKnownAvailabilityGroup = 'AG01' + $mockUnknownAvailabilityGroup = 'UnknownAG' + $mockKnownListenerName = 'AGListener' + $mockUnknownListenerName = 'UnknownListener' + $mockKnownPortNumber = 5031 + $mockUnknownPortNumber = 9001 + + # Static parameter values + $mockNodeName = 'localhost' + $mockInstanceName = 'MSSQLSERVER' + $mockDynamicAvailabilityGroup = $mockKnownAvailabilityGroup + $mockDynamicListenerName = $mockKnownListenerName + $mockDynamicPortNumber = $mockKnownPortNumber + $mockDynamicIsDhcp = $true + $script:mockMethodDropRan = $false + + $mockConnectSql = { + return New-Object Object | + Add-Member ScriptProperty AvailabilityGroups { + return @( + @{ + $mockDynamicAvailabilityGroup = New-Object Object | + Add-Member ScriptProperty AvailabilityGroupListeners { + @( + @{ + $mockDynamicListenerName = New-Object Object | + Add-Member NoteProperty PortNumber $mockDynamicPortNumber -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 $mockDynamicIsDhcp -PassThru | + Add-Member NoteProperty IPAddress '192.168.0.1' -PassThru | + Add-Member NoteProperty SubnetMask '255.255.255.0' -PassThru + ) + ) + } -PassThru | + Add-Member ScriptMethod Drop { + $script:mockMethodDropRan = $true + } -PassThru -Force + } + ) + } -PassThru -Force + } + ) + } -PassThru -Force } - 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' - } - - 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 - } + $defaultParameters = @{ + InstanceName = $mockInstanceName + NodeName = $mockNodeName + Name = $mockKnownListenerName + AvailabilityGroup = $mockKnownAvailabilityGroup } - Assert-VerifiableMocks - } + #endregion Pester Test Initialization - 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' { + Describe 'xSQLServerAvailabilityGroupListener\Get-TargetResource' { + BeforeEach { $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 + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + } + + Context 'When the system is not in the desired state' { + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -Verifiable + + It 'Should return the desired state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $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 = Get-TargetResource @testParameters + $result.IpAddress | Should Be $null + } - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should not return port' { + $result = Get-TargetResource @testParameters + $result.Port | Should Be 0 + } - 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 DHCP is not used' { + $result = Get-TargetResource @testParameters + $result.DHCP | Should Be $false + } - 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 call the mock function Get-SQLAlwaysOnAvailabilityGroupListener' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -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 - } + Context 'When the system is in the desired state, without DHCP' { + It 'Should return the desired state as present' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $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 return correct IP address' { + $result = Get-TargetResource @testParameters + $result.IpAddress | Should Be '192.168.0.1/255.255.255.0' + } - 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 correct port' { + $result = Get-TargetResource @testParameters + $result.Port | Should Be $mockKnownPortNumber + } - 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' { + $mockDynamicIsDhcp = $false - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Get-TargetResource @testParameters + $result.DHCP | Should Be $false + } - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -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 + Context 'When the system is in the desired state, with DHCP' { + It 'Should return the desired state as present' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + } - 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 - } + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $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 = Get-TargetResource @testParameters + $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 = Get-TargetResource @testParameters + $result.Port | Should Be $mockKnownPortNumber + } + + It 'Should return that DHCP is used' { + $mockDynamicIsDhcp = $true + + $result = Get-TargetResource @testParameters + $result.DHCP | Should Be $true + } + + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When Get-SQLAlwaysOnAvailabilityGroupListener throws an error' { + # Setting dynamic mock to an availability group that the test is not expecting. + $mockDynamicAvailabilityGroup = $mockUnknownAvailabilityGroup + + It 'Should throw the correct error' { + { Get-TargetResource @testParameters } | Should Throw 'Trying to make a change to a listener that does not exist. InnerException: Unable to locate the availability group 'AG01' on the instance 'MSSQLSERVER'.' + } + } + + Assert-VerifiableMocks } - 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' { + Describe 'xSQLServerAvailabilityGroupListener\Test-TargetResource' { + BeforeEach { $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 + 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 += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -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 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 + } -Verifiable + + It 'Should return that desired state is absent when wanted desired state is to be Absent' { + $testParameters += @{ + Ensure = 'Absent' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -Scope It + } + + It 'Should return that desired state is absent when IP address is different' { + $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 -Scope It + } + + It 'Should return that desired state is absent when DHCP is absent but should be present' { + $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 -Scope It + } + + It 'Should return that desired state is absent when DHCP is the only set parameter' { + $testParameters += @{ + DHCP = $true + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -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 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 + } -PassThru -Force + } -Verifiable + + It 'Should return that desired state is absent when port is different' { + $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 -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 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 - } - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - Assert-VerifiableMocks - } + } -PassThru -Force + } -Verifiable + + It 'Should return that desired state is absent when DHCP is present but should be absent' { + $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 -Scope It + } + + It 'Should return that desired state is absent when IP address is the only set parameter' { + $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 -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 + } -Verifiable + + It 'Should return that desired state is absent when port is the only set parameter' { + $testParameters += @{ + Port = 5030 + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -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 += @{ + Ensure = 'Absent' + IpAddress = '192.168.10.45/255.255.252.0' + Port = 5030 + DHCP = $false + } + + Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -Verifiable + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -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 + } -Verifiable + + It 'Should return that desired state is present when wanted desired state is to be Present, without DHCP' { + $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 -Scope It + } + + It 'Should return that desired state is present when IP address is the only set parameter' { + $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 -Scope It + } + + It 'Should return that desired state is present when port is the only set parameter' { + $testParameters += @{ + Port = 5030 + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -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 + ) + ) + } -PassThru -Force + } -Verifiable - 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 + It 'Should return that desired state is present when wanted desired state is to be Present, with DHCP' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = 5030 + DHCP = $true + } - 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 - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -Scope It + } - Set-TargetResource @testParameters | Out-Null + It 'Should return that desired state is present when DHCP is the only set parameter' { + $testParameters += @{ + DHCP = $true + } - 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 + $result = Test-TargetResource @testParameters + $result | Should Be $true - 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 + Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -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 - } + Context 'When Get-TargetResource returns $null' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith { + return $null + } - 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 + { Test-TargetResource @testParameters } | Should Throw 'Got unexpected result from Get-TargetResource. No change is made.' + } } - 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 - } + Assert-VerifiableMocks } - 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' { + Describe 'xSQLServerAvailabilityGroupListener\Set-TargetResource' { + BeforeEach { $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 - } + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable -Scope It + Mock -CommandName New-SqlAvailabilityGroupListener -MockWith {} -Verifiable + Mock -CommandName Set-SqlAvailabilityGroupListener -MockWith {} -Verifiable + Mock -CommandName Add-SqlAvailabilityGroupListenerStaticIp -MockWith {} -Verifiable + } + + Context 'When the system is not in the desired state' { + $mockDynamicListenerName = $mockUnknownListenerName + + It 'Should call the cmdlet New-SqlAvailabilityGroupListener when system is not in desired state, when using Static IP' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.45/255.255.252.0' + Port = $mockKnownPortNumber + DHCP = $false + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 1 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicListenerName = $mockUnknownListenerName + + It 'Should call the cmdlet New-SqlAvailabilityGroupListener when system is not in desired state, when using DHCP and specific DhcpSubnet' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.10.1/255.255.252.0' + Port = $mockKnownPortNumber + DHCP = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 1 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicListenerName = $mockUnknownListenerName + + It 'Should call the cmdlet New-SqlAvailabilityGroupListener when system is not in desired state, when using DHCP and server default DhcpSubnet' { + $testParameters += @{ + Ensure = 'Present' + Port = $mockKnownPortNumber + DHCP = $true + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 1 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicIsDhcp = $false + $mockDynamicListenerName = $mockKnownListenerName + $mockDynamicPortNumber = $mockKnownPortNumber + + It 'Should throw when trying to change an existing IP address' { + $testParameters += @{ + IpAddress = '10.0.0.1/255.255.252.0' + Port = $mockKnownPortNumber + DHCP = $false + } + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicIsDhcp = $false + $mockDynamicListenerName = $mockKnownListenerName + $mockDynamicPortNumber = $mockKnownPortNumber + + It 'Should throw when trying to change from static IP to DHCP' { + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + DHCP = $true + } + + { Set-TargetResource @testParameters } | Should Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicIsDhcp = $false + $mockDynamicListenerName = $mockKnownListenerName + $mockDynamicPortNumber = $mockKnownPortNumber + + It 'Should call the cmdlet Add-SqlAvailabilityGroupListenerStaticIp, when adding another IP address, and system is not in desired state' { + $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 } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 1 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 1 -Scope It + } + + $mockDynamicIsDhcp = $false + $mockDynamicListenerName = $mockKnownListenerName + $mockDynamicPortNumber = $mockKnownPortNumber + + It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + DHCP = $false + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicListenerName = $mockKnownListenerName + $script:mockMethodDropRan = $false # This is set to $true when Drop() method is called. make sure we start the test with $false. + + It 'Should not call the any cmdlet *-SqlAvailability* or the the Drop() method when system is in desired state and ensure is set to ''Absent''' { + $testParameters += @{ + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodDropRan | Should Be $true # Should have made one call to the Drop() method. + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicAvailabilityGroup = $mockUnknownAvailabilityGroup + $mockDynamicListenerName = $mockUnknownListenerName + + It 'Should throw the correct error when availability group is not found and Ensure is set to ''Present''' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + DHCP = $false + } + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } + + { Set-TargetResource @testParameters } | Should Throw 'Unable to locate the availability group ''AG01'' on the instance ''MSSQLSERVER''.' + } + + It 'Should throw the correct error when availability group is not found and Ensure is set to ''Absent''' { + $testParameters += @{ + Ensure = 'Absent' + } + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + } + } + + { Set-TargetResource @testParameters } | Should Throw 'Unable to locate the availability group ''AG01'' on the instance ''MSSQLSERVER''.' + } + + $mockDynamicAvailabilityGroup = $mockKnownAvailabilityGroup + $mockDynamicListenerName = $mockUnknownListenerName + + It 'Should throw the correct error when listener is not found and Ensure is set to ''Absent''' { + $testParameters += @{ + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Throw 'Trying to make a change to a listener that does not exist.' + } + + It 'Should throw the correct error when listener is not found and Ensure is set to ''Present''' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + DHCP = $false + } + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = $mockUnknownListenerName + AvailabilityGroup = $mockKnownAvailabilityGroup + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + DHCP = $false + } + } + + { Set-TargetResource @testParameters } | Should Throw 'Trying to make a change to a listener that does not exist.' + } + + $mockDynamicAvailabilityGroup = $mockUnknownAvailabilityGroup + $mockDynamicListenerName = $mockUnknownListenerName + + It 'Should throw the correct error when availability group is not found and Ensure is set to ''Present''' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + DHCP = $false + } + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Name = $mockUnknownListenerName + AvailabilityGroup = $mockUnknownAvailabilityGroup + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + DHCP = $false + } + } + + { Set-TargetResource @testParameters } | Should Throw 'Unable to locate the availability group ''AG01'' on the instance ''MSSQLSERVER''.' + } + } + + Context 'When the system is in the desired state' { + $mockDynamicIsDhcp = $false + $mockDynamicListenerName = $mockKnownListenerName + $mockDynamicPortNumber = $mockKnownPortNumber + + It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state' { + $testParameters += @{ + Ensure = 'Present' + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicIsDhcp = $false + $mockDynamicListenerName = $mockKnownListenerName + $mockDynamicPortNumber = $mockKnownPortNumber + + It 'Should not call the any cmdlet *-SqlAvailability* when system is in desired state (without ensure parameter)' { + $testParameters += @{ + IpAddress = '192.168.0.1/255.255.255.0' + Port = $mockKnownPortNumber + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + } + + $mockDynamicListenerName = $mockUnknownListenerName + $script:mockMethodDropRan = $false # This is set to $true when Drop() method is called. make sure we start the test with $false. + + It 'Should not call the any cmdlet *-SqlAvailability* or the the Drop() method when system is in desired state and ensure is set to ''Absent''' { + $testParameters += @{ + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodDropRan | Should Be $false # Should not have called Drop() method. + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + Assert-MockCalled New-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Set-SqlAvailabilityGroupListener -Exactly -Times 0 -Scope It + Assert-MockCalled Add-SqlAvailabilityGroupListenerStaticIp -Exactly -Times 0 -Scope It + + } + } + + Context 'When Get-TargetResource returns $null' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith { + return $null + } + + { Set-TargetResource @testParameters } | Should Throw 'Got unexpected result from Get-TargetResource. No change is made.' + } + } + + Assert-VerifiableMocks } - - Assert-VerifiableMocks } } finally { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 index faa2ddb2c..c0e51494d 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabasePermission.Tests.ps1 @@ -1,359 +1,860 @@ -$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 -} +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerDatabasePermission' + +#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 { + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockSqlServerName = 'localhost' + $mockSqlServerInstanceName = 'MSSQLSERVER' + $mockSqlDatabaseName = 'AdventureWorks' + $mockSqlServerLogin = 'Zebes\SamusAran' + $mockSqlServerLoginUnknown = 'Elysia\Chozo' + $mockLoginType = 'WindowsUser' + $mockInvalidOperationEnumDatabasePermissions = $false + $mockInvalidOperationForCreateMethod = $false + $mockExpectedSqlServerLogin = 'Zebes\SamusAran' + $mockSqlPermissionState = 'Grant' + + $mockSqlPermissionType01 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet -ArgumentList ($true, $false) + $mockSqlPermissionType02 = New-Object -TypeName Microsoft.SqlServer.Management.Smo.DatabasePermissionSet -ArgumentList ($false, $true) + + $script:mockMethodGrantRan = $false + $script:mockMethodDenyRan = $false + $script:mockMethodRevokeRan = $false + $script:mockMethodCreateLoginRan = $false + + # Default parameters that are used for the It-blocks + $mockDefaultParameters = @{ + SQLInstanceName = $mockSqlServerInstanceName + SQLServer = $mockSqlServerName + } + + #region Function mocks + $mockConnectSQL = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name InstanceName -Value $mockSqlServerInstanceName -PassThru | + Add-Member -MemberType NoteProperty -Name ComputerNamePhysicalNetBIOS -Value $mockSqlServerName -PassThru | + Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + $mockSqlDatabaseName = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlDatabaseName -PassThru | + Add-Member -MemberType ScriptProperty -Name Users -Value { + return @{ + $mockSqlServerLogin = @(( + New-Object Object | + Add-Member -MemberType ScriptMethod -Name IsMember -Value { + return $true + } -PassThru + )) + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name EnumDatabasePermissions -Value { + param + ( + [Parameter()] + [System.String] + $SqlServerLogin + ) + if ($mockInvalidOperationEnumDatabasePermissions) + { + throw 'Mock EnumDatabasePermissions Method was called with invalid operation.' + } + + if ( $SqlServerLogin -eq $mockExpectedSqlServerLogin ) + { + $mockEnumDatabasePermissions = @() + $mockEnumDatabasePermissions += New-Object Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $mockSqlPermissionType01 -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value $mockSqlPermissionState -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value $mockExpectedSqlServerLogin -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'Database' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value $mockSqlDatabaseName -PassThru + $mockEnumDatabasePermissions += New-Object Object | + Add-Member -MemberType NoteProperty -Name PermissionType -Value $mockSqlPermissionType02 -PassThru | + Add-Member -MemberType NoteProperty -Name PermissionState -Value $mockSqlPermissionState -PassThru | + Add-Member -MemberType NoteProperty -Name Grantee -Value $mockExpectedSqlServerLogin -PassThru | + Add-Member -MemberType NoteProperty -Name GrantorType -Value 'User' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectClass -Value 'Database' -PassThru | + Add-Member -MemberType NoteProperty -Name ObjectName -Value $mockSqlDatabaseName -PassThru + + $mockEnumDatabasePermissions + } + else + { + return $null + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name Grant -Value { + param + ( + [Parameter()] + [System.Object] + $permissionSet, + + [Parameter()] + [System.String] + $SqlServerLogin + ) + + $script:mockMethodGrantRan = $true + + if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) + { + throw "Called mocked Grant() method without setting the right login name. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedSqlServerLogin, $SqlServerLogin + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name Revoke -Value { + param + ( + [Parameter()] + [System.Object] + $permissionSet, + + [Parameter()] + [System.String] + $SqlServerLogin + ) + + $script:mockMethodRevokeRan = $true + + if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) + { + throw "Called mocked Revoke() method without setting the right login name. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedSqlServerLogin, $SqlServerLogin + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name Deny -Value { + param + ( + [Parameter()] + [System.Object] + $permissionSet, + + [Parameter()] + [System.String] + $SqlServerLogin + ) + + $script:mockMethodDenyRan = $true + + if ( $SqlServerLogin -ne $mockExpectedSqlServerLogin ) + { + throw "Called mocked Deny() method without setting the right login name. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedSqlServerLogin, $SqlServerLogin + } + } -PassThru -Force + )) + } + } -PassThru -Force | + Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + $mockSqlServerLogin = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockLoginType -PassThru + )) + $mockSqlServerLoginUnknown= @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockLoginType -PassThru + )) + } + } -PassThru -Force + ) + ) + } + + $mockNewObjectUser = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlServerLoginUnknown -PassThru | + Add-Member -MemberType NoteProperty -Name Login -Value $mockSqlServerLoginUnknown -PassThru | + Add-Member -MemberType ScriptMethod -Name Create -Value { + $script:mockMethodCreateLoginRan = $true + + if ($mockInvalidOperationForCreateMethod) + { + throw 'Mock Create Method was called with invalid operation.' + } + if ( $this.Name -ne $mockExpectedSqlServerLogin ) + { + throw "Called mocked Create() method without adding the right user. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedSqlServerLogin, $this.Name + } + } -PassThru -Force + ) + ) + } + + #endregion + + Describe "MSFT_xSQLServerDatabasePermission\Get-TargetResource" -Tag 'Get'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + } + + Context 'When passing values to parameters and database name does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = 'unknownDatabaseName' + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + $throwInvalidOperation = ("Database 'unknownDatabaseName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") + + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When passing values to parameters and login name does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = 'unknownLoginName' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + $throwInvalidOperation = ("Login 'unknownLoginName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") + + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When passing values to parameters and database name and login name do exist' { + It 'Should throw the correct error with EnumDatabasePermissions method' { + $mockInvalidOperationEnumDatabasePermissions = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + $throwInvalidOperation = ('Failed to get permission for login named Zebes\SamusAran of ' + ` + 'the database named AdventureWorks on localhost\MSSQLSERVER.') + + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state and ensure is set to Absent' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update','Select' ) + } + + It 'Should return the state as absent when the desired permission does not exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Absent' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should not return the state as absent when the desired permission does not exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Not Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + } + + It 'Should return the state as absent when the desired permission does not exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update','Select' ) + } + + It 'Should not return the state as absent when the desired permission does not exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Not Be 'Present' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "MSFT_xSQLServerDatabasePermission\Test-TargetResource" -Tag 'Test'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + } + + Context 'When passing values to parameters and database name does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = 'unknownDatabaseName' + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + $throwInvalidOperation = ("Database 'unknownDatabaseName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") + + { Test-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When passing values to parameters and login name does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = 'unknownLoginName' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + $throwInvalidOperation = ("Login 'unknownLoginName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") + + { Test-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When passing values to parameters and database name and login name do exist' { + It 'Should throw the correct error with EnumDatabasePermissions method' { + $mockInvalidOperationEnumDatabasePermissions = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + $throwInvalidOperation = ('Failed to get permission for login named Zebes\SamusAran of ' + ` + 'the database named AdventureWorks on localhost\MSSQLSERVER.') + + { Test-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state and ensure is set to Absent' { + It 'Should return the state as true when the desired permission does not exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update','Select' ) + Ensure = 'Absent' + } + + Test-TargetResource @testParameters | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Absent' { + It 'Should return the state as false when the desired permission does not exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Absent' + } + + Test-TargetResource @testParameters | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Present' { + It 'Should return the state as false when the desired permission does not exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update','Select' ) + Ensure = 'Present' + } + + Test-TargetResource @testParameters | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state and ensure is set to Present' { + It 'Should return the state as true when the desired permission does exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + Test-TargetResource @testParameters | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "MSFT_xSQLServerDatabasePermission\Set-TargetResource" -Tag 'Set'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + Mock -CommandName New-Object -MockWith $mockNewObjectUser -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Verifiable + + $script:mockMethodGrantRan = $false + $script:mockMethodDenyRan = $false + $script:mockMethodRevokeRan = $false + $script:mockMethodCreateLoginRan = $false + } + + Context 'When passing values to parameters and database name does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = 'unknownDatabaseName' + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + $throwInvalidOperation = ("Database 'unknownDatabaseName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When passing values to parameters and login name does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = 'unknownLoginName' + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + $throwInvalidOperation = ("Login 'unknownLoginName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the login cannot be created' { + It 'Should throw the correct error' { + $mockInvalidOperationForCreateMethod = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLoginUnknown + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + $throwInvalidOperation = ('Failed adding the login Elysia\Chozo ' + ` + 'as a user of the database AdventureWorks, ' + ` + 'on the instance localhost\MSSQLSERVER.') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + $script:mockMethodCreateLoginRan | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state' { + Context 'When the mock methods fail (testing the test)' { + BeforeAll { + $throwInvalidOperation = ('Failed to set permission for login named ' + ` + 'Zebes\SamusAran of the database named ' + ` + 'AdventureWorks on localhost\MSSQLSERVER.') + + $mockExpectedSqlServerLogin = $mockSqlServerLoginUnknown + } + + It 'Should throw the correct error when mock Grant() method is called' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + $script:mockMethodGrantRan | Should Be $true + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $false + } + + It 'Should throw the correct error when mock Grant() method is called (for GrantWithGrant)' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'GrantWithGrant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + $script:mockMethodGrantRan | Should Be $true + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $false + } + + + It 'Should throw the correct error when mock Deny() method is called' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Deny' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodDenyRan | Should Be $true + $script:mockMethodRevokeRan | Should Be $false + } + + It 'Should throw the correct error when mock Revoke() method is called' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $true + } + + It 'Should throw the correct error when mock Revoke() method is called' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'GrantWithGrant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $true + } + } + + Context 'When Ensure is set to Present' { + Context 'When the login does not exist' { + It 'Should create the login without throwing an error' { + $mockInvalidOperationForCreateMethod = $false + $mockExpectedSqlServerLogin = $mockSqlServerLoginUnknown + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLoginUnknown + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + { Set-TargetResource @testParameters } | Should Not Throw + + $script:mockMethodCreateLoginRan | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + It 'Should call the method Grant() without throwing' { + $mockExpectedSqlServerLogin = $mockSqlServerLogin + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + { Set-TargetResource @testParameters } | Should Not Throw + + $script:mockMethodGrantRan | Should Be $true + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should call the method Grant() (WithGrant) without throwing' { + $mockExpectedSqlServerLogin = $mockSqlServerLogin + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'GrantWithGrant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + { Set-TargetResource @testParameters } | Should Not Throw + + $script:mockMethodGrantRan | Should Be $true + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should call the method Deny() without throwing' { + $mockExpectedSqlServerLogin = $mockSqlServerLogin + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Deny' + Permissions = @( 'Connect','Update' ) + Ensure = 'Present' + } + + { Set-TargetResource @testParameters } | Should Not Throw + + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodDenyRan | Should Be $true + $script:mockMethodRevokeRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When Ensure is set to Absent' { + It 'Should call the method Revoke() for permission state ''Grant'' without throwing' { + $mockExpectedSqlServerLogin = $mockSqlServerLogin + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Grant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Not Throw + + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should call the method Revoke() for permission state ''GrantWithGrant'' without throwing' { + $mockExpectedSqlServerLogin = $mockSqlServerLogin + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'GrantWithGrant' + Permissions = @( 'Connect','Update' ) + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Not Throw + + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should call the method Revoke() for permission state ''Deny'' without throwing' { + $mockExpectedSqlServerLogin = $mockSqlServerLogin + $testParameters = $mockDefaultParameters + $testParameters += @{ + Database = $mockSqlDatabaseName + Name = $mockSqlServerLogin + PermissionState = 'Deny' + Permissions = @( 'Connect','Update' ) + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Not Throw + + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodDenyRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + } + + Assert-VerifiableMocks + } + } +} +finally +{ + Invoke-TestCleanup +}#endregion diff --git a/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 index 91abe6df2..6540dcb4b 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabaseRecoveryModel.Tests.ps1 @@ -3,10 +3,10 @@ $script:DSCResourceName = 'MSFT_xSQLServerDatabaseRecoveryModel' #region HEADER -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +# 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'))) ) + (-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\')) } @@ -16,221 +16,249 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion HEADER +function Invoke-TestSetup { +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + # 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 - } + Invoke-TestSetup + InModuleScope $script:DSCResourceName { + $mockSqlServerName = 'localhost' + $mockSqlServerInstanceName = 'MSSQLSERVER' + $mockSqlDatabaseName = 'AdventureWorks' + $mockSqlDatabaseRecoveryModel = 'Simple' + $mockInvalidOperationForAlterMethod = $false + $mockExpectedRecoveryModel = 'Simple' + + # Default parameters that are used for the It-blocks + $mockDefaultParameters = @{ + SQLInstanceName = $mockSqlServerInstanceName + SQLServer = $mockSqlServerName + } + + #region Function mocks + $mockConnectSQL = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name InstanceName -Value $mockSqlServerInstanceName -PassThru | + Add-Member -MemberType NoteProperty -Name ComputerNamePhysicalNetBIOS -Value $mockSqlServerName -PassThru | + Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + $mockSqlDatabaseName = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlDatabaseName -PassThru | + Add-Member -MemberType NoteProperty -Name RecoveryModel -Value $mockSqlDatabaseRecoveryModel -PassThru | + Add-Member -MemberType ScriptMethod -Name Alter -Value { + if ($mockInvalidOperationForAlterMethod) + { + throw 'Mock Alter Method was called with invalid operation.' + } + + if ( $this.RecoveryModel -ne $mockExpectedRecoveryModel ) + { + throw "Called Alter Drop() method without setting the right recovery model. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedRecoveryModel, $this.RecoveryModel + } + } -PassThru + ) + } + } -PassThru -Force + ) + ) + } + #endregion - #endregion Pester Test Initialization + Describe "MSFT_xSQLServerDatabaseRecoveryModel\Get-TargetResource" -Tag 'Get'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + } - 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') ) ) + Context 'When passing values to parameters and database does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + RecoveryModel = 'Full' } - } -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 + $throwInvalidOperation = ("Database 'UnknownDatabase' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") - It 'Should return null for RecoveryModel' { - $result.RecoveryModel | Should Be $null - } + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } - 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 -Scope Context + } } + + Context 'When the system is not in the desired state' { + It 'Should return wrong RecoveryModel' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' + } - 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 + $result.RecoveryModel | Should Not Be $testParameters.RecoveryModel + } - $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.Name | Should Be $testParameters.Name + } - It 'Should return wrong RecoveryModel' { - $result.RecoveryModel | Should Not Be $testParameters.RecoveryModel + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } + + Context 'When the system is in the desired state for a database' { + It 'Should return the correct RecoveryModel' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' + } + + $result = Get-TargetResource @testParameters + $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 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 + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } + + Assert-VerifiableMocks } - 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 + Describe "MSFT_xSQLServerDatabaseRecoveryModel\Test-TargetResource" -Tag 'Test'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable } - 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 - } + 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 = $mockDefaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' + } - 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 + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -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') ) ) + Context 'When the system is in the desired state' { + It 'Should return the state as true when desired recovery model is correct' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Simple' } - } -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 $true } - $result = Test-TargetResource @testParameters - $result | Should Be $false + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-VerifiableMocks } + + Describe "MSFT_xSQLServerDatabaseRecoveryModel\Set-TargetResource" -Tag 'Set'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + } - 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' - } + Context 'When the system is not in the desired state, and database does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'UnknownDatabase' + RecoveryModel = 'Full' + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + $throwInvalidOperation = ("Database 'UnknownDatabase' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } - } - 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') ) ) + Context 'When the system is not in the desired state' { + It 'Should not throw when calling the alter method when desired recovery model should be set' { + $mockExpectedRecoveryModel = 'Full' + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' } - } -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' + + { Set-TargetResource @testParameters } | Should Not Throw } - 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 + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } - } - 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' - } + Context 'When the system is not in the desired state' { + It 'Should throw when calling the alter method when desired recovery model should be set' { + $mockInvalidOperationForAlterMethod = $true + $mockExpectedRecoveryModel = 'Full' + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'AdventureWorks' + RecoveryModel = 'Full' + } - Mock -CommandName Set-SqlDatabaseRecoveryModel -MockWith { } -ModuleName $script:DSCResourceName -Verifiable + $throwInvalidOperation = ('Exception calling "Alter" with "0" argument(s): ' + + '"Mock Alter Method was called with invalid operation."') - Set-TargetResource @testParameters + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Set-SqlDatabaseRecoveryModel -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } + + Assert-VerifiableMocks } } } finally { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xSQLServerDatabaseRole.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerDatabaseRole.Tests.ps1 index 3eabd92c6..079ba4f33 100644 --- a/Tests/Unit/MSFT_xSQLServerDatabaseRole.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerDatabaseRole.Tests.ps1 @@ -3,439 +3,722 @@ $script:DSCResourceName = 'MSFT_xSQLServerDatabaseRole' #region HEADER -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +# 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 +Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion HEADER +function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + # Begin Testing try { - #region Pester Test Initialization + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockSqlServerName = 'localhost' + $mockSqlServerInstanceName = 'MSSQLSERVER' + $mockSqlDatabaseName = 'AdventureWorks' + $mockSqlServerLogin = 'John' + $mockSqlServerLoginOne = 'CONTOSO\KingJulian' + $mockSqlServerLoginTwo = 'CONTOSO\SQLAdmin' + $mockSqlServerLoginType = 'WindowsUser' + $mockSqlDatabaseRole = 'MyRole' + $mockSqlDatabaseRoleSecond = 'MySecondRole' + $mockExpectedSqlDatabaseRole = 'MyRole' + $mockInvalidOperationForAddMemberMethod = $false + $mockInvalidOperationForDropMemberMethod = $false + $mockInvalidOperationForCreateMethod = $false + $mockExpectedForAddMemberMethod = 'MySecondRole' + $mockExpectedForDropMemberMethod = 'MyRole' + $mockExpectedForCreateMethod = 'John' + + # Default parameters that are used for the It-blocks + $mockDefaultParameters = @{ + SQLInstanceName = $mockSqlServerInstanceName + SQLServer = $mockSqlServerName + } + + #region Function mocks + $mockConnectSQL = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name InstanceName -Value $mockSqlServerInstanceName -PassThru | + Add-Member -MemberType NoteProperty -Name ComputerNamePhysicalNetBIOS -Value $mockSqlServerName -PassThru | + Add-Member -MemberType ScriptProperty -Name Databases -Value { + return @{ + $mockSqlDatabaseName = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlDatabaseName -PassThru | + Add-Member -MemberType ScriptProperty -Name Users -Value { + return @{ + $mockSqlServerLoginOne = @(( + New-Object Object | + Add-Member -MemberType ScriptMethod -Name IsMember -Value { + param( + [String] + $mockSqlDatabaseRole + ) + if ( $mockSqlDatabaseRole -eq $mockExpectedSqlDatabaseRole ) + { + return $true + } + else + { + return $false + } + } -PassThru + )) + $mockSqlServerLoginTwo = @(( + New-Object Object | + Add-Member -MemberType ScriptMethod -Name IsMember -Value { + return $true + } -PassThru + )) + } + } -PassThru | + Add-Member -MemberType ScriptProperty -Name Roles -Value { + return @{ + $mockSqlDatabaseRole = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlDatabaseRole -PassThru | + Add-Member -MemberType ScriptMethod -Name AddMember -Value { + param( + [String] + $mockSqlServerLogin + ) + if ($mockInvalidOperationForAddMemberMethod) + { + throw 'Mock AddMember Method was called with invalid operation.' + } + if ( $this.Name -ne $mockExpectedForAddMemberMethod ) + { + throw "Called mocked AddMember() method without adding the right user. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedForAddMemberMethod, $this.Name + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name DropMember -Value { + param( + [String] + $mockSqlServerLogin + ) + if ($mockInvalidOperationForDropMemberMethod) + { + throw 'Mock DropMember Method was called with invalid operation.' + } + if ( $this.Name -ne $mockExpectedForDropMemberMethod ) + { + throw "Called mocked Drop() method without adding the right user. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedForDropMemberMethod, $this.Name + } + } -PassThru + )) + $mockSqlDatabaseRoleSecond = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlDatabaseRoleSecond -PassThru | + Add-Member -MemberType ScriptMethod -Name AddMember -Value { + param( + [String] + $mockSqlServerLogin + ) + if ($mockInvalidOperationForAddMemberMethod) + { + throw 'Mock AddMember Method was called with invalid operation.' + } + if ( $this.Name -ne $mockExpectedForAddMemberMethod ) + { + throw "Called mocked AddMember() method without adding the right user. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedForAddMemberMethod, $this.Name + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name DropMember -Value { + param( + [String] + $mockSqlServerLogin + ) + if ($mockInvalidOperationForDropMemberMethod) + { + throw 'Mock DropMember Method was called with invalid operation.' + } + if ( $this.Name -ne $mockExpectedForDropMemberMethod ) + { + throw "Called mocked Drop() method without adding the right user. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedForDropMemberMethod, $this.Name + } + } -PassThru + )) + } + }-PassThru -Force + )) + } + } -PassThru -Force | + Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + $mockSqlServerLoginOne = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockSqlServerLoginType -PassThru + )) + $mockSqlServerLoginTwo = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockSqlServerLoginType -PassThru + )) + $mockSqlServerLogin = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockSqlServerLoginType -PassThru + )) + } + } -PassThru -Force + + ) + ) + } - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + $mockNewObjectUser = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlServerLogin -PassThru | + Add-Member -MemberType NoteProperty -Name Login -Value $mockSqlServerLogin -PassThru | + Add-Member -MemberType ScriptMethod -Name Create -Value { + if ($mockInvalidOperationForCreateMethod) + { + throw 'Mock Create Method was called with invalid operation.' + } + if ( $this.Name -ne $mockExpectedForCreateMethod ) + { + throw "Called mocked Create() method without adding the right user. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedForCreateMethod, $this.Name + } + } -PassThru -Force + ) + ) + } + #endregion - $nodeName = 'localhost' - $instanceName = 'MSSQLSERVER' - $loginName = 'COMPANY\Stacy' - $loginNameReturnsAbsent = 'John' - $databaseName = 'MyDatabase' - $roleName = 'MyRole' - $secondRoleName = 'MySecondRole' + Describe "MSFT_xSQLServerDatabaseRole\Get-TargetResource" -Tag 'Get'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + } - $unknownLoginName = 'UnknownLogin' - $unknownDatabaseName = 'UnknownDatabase' - $unknownRoleName = 'UnknownRole' + Context 'When passing values to parameters and database name does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = 'unknownDatabaseName' + Role = $mockSqlDatabaseRole + } + $throwInvalidOperation = ("Database 'unknownDatabaseName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") - $defaultParameters = @{ - SQLInstanceName = $instanceName - SQLServer = $nodeName - } + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } - #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 Object | - Add-Member NoteProperty LoginType 'WindowsUser' -PassThru ) ) - 'John' = @( ( New-Object Object | - Add-Member NoteProperty LoginType 'WindowsUser' -PassThru ) ) + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } + + Context 'When passing values to parameters and role does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = 'unknownRoleName' } - } -PassThru | - Add-Member ScriptProperty Databases { - return @{ - 'MyDatabase' = @( ( New-Object Object | - Add-Member ScriptProperty Users { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Object | - Add-Member ScriptMethod IsMember { - return $true - } -PassThru ) ) - 'John' = @( ( New-Object Object | - Add-Member ScriptMethod IsMember { - param( - [String] $Role - ) - if( $Role -eq 'MySecondRole' ) { - return $true - } else { - return $false - } - } -PassThru ) ) } - } -PassThru | - Add-Member ScriptProperty Roles { - return @{ - 'MyRole' = @( ( New-Object Object ) ) - 'MySecondRole' = @( ( New-Object Object ) ) - } - } -PassThru ) ) + + $throwInvalidOperation = ("Role 'unknownRoleName' does not exist on database " + ` + "'AdventureWorks' on SQL server 'localhost\MSSQLSERVER'.") + + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } + + Context 'When passing values to parameters and multiple values to Role parameter' { + It 'Should not throw' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = @($mockSqlDatabaseRole,$mockSqlDatabaseRoleSecond) } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - Context 'When passing values to parameters' { - It 'Should throw when database name does not exist' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginName - Database = $unknownDatabaseName - Role = $roleName + { Get-TargetResource @testParameters } | Should Not Throw } - { Get-TargetResource @testParameters } | Should Throw + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } + + Context 'When passing values to parameters and login does not exist' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = 'unknownLoginName' + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRole + } + + $throwInvalidOperation = ("Login 'unknownLoginName' does not exist " + ` + "on SQL server 'localhost\MSSQLSERVER'.") - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } - It 'Should throw when role does not exist' { - $testParameters = $defaultParameters + Context 'When the system is not in the desired state, with one role' { + $testParameters = $mockDefaultParameters $testParameters += @{ - Name = $loginName - Database = $databaseName - Role = $unknownRoleName + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRoleSecond } + + It 'Should return the state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' - { Get-TargetResource @testParameters } | Should Throw + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + It 'Should not return any granted roles' { + $result = Get-TargetResource @testParameters + $result.Role | Should Be $null - It 'Should not throw when adding two roles' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginName - Database = $databaseName - Role = @( $roleName, $secondRoleName ) + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It } - { Get-TargetResource @testParameters } | Should Not Throw + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database + $result.Name | Should Be $testParameters.Name - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - It 'Should throw when login does not exist' { - $testParameters = $defaultParameters + Context 'When the system is not in the desired state, with two roles' { + $testParameters = $mockDefaultParameters $testParameters += @{ - Name = $unknownLoginName - Database = $databaseName - Role = $roleName + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = @($mockSqlDatabaseRole,$mockSqlDatabaseRoleSecond) } - { Get-TargetResource @testParameters } | Should Throw + It 'Should return the state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } - Context 'When the system is not in the desired state, with one role' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginNameReturnsAbsent - Database = $databaseName - Role = $roleName - } + It 'Should only return the one granted role' { + $result = Get-TargetResource @testParameters + $result.Role | Should Be $mockSqlDatabaseRole - $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } - It 'Should return the state as absent' { - $result.Ensure | Should Be 'Absent' - } + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database + $result.Name | Should Be $testParameters.Name - It 'Should not return any granted roles' { - $result.Role | Should Be $null + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } + + Context 'When the system is not in the desired state, and login is not a member of the database' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLogin + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRole + } - 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 - $result.Name | Should Be $testParameters.Name - } + It 'Should return the state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database + $result.Name | Should Be $testParameters.Name - Context 'When the system is not in the desired state, with two roles' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginNameReturnsAbsent - Database = $databaseName - Role = @( $roleName, $secondRoleName ) + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - $result = Get-TargetResource @testParameters + Context 'When the system is in the desired state for a Windows user' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRole + } - It 'Should return the state as absent' { - $result.Ensure | Should Be 'Absent' - } + It 'Should return the state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' - It 'Should only return the one granted role' { - $result.Role | Should Be $secondRoleName - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -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.Database | Should Be $testParameters.Database - $result.Name | Should Be $testParameters.Name - } + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.Database | Should Be $testParameters.Database + $result.Name | Should Be $testParameters.Name + $result.Role | Should Be $testParameters.Role - It 'Should call the mock function Connect-SQL' { - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } + + Assert-VerifiableMocks } - - Context 'When the system is in the desired state for a Windows user' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginName - Database = $databaseName - Role = $roleName + + Describe "MSFT_xSQLServerDatabaseRole\Test-TargetResource" -Tag 'Test'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable } - $result = Get-TargetResource @testParameters + Context 'When the system is not in the desired state and Ensure is set to Present' { + It 'Should return the state as false when one desired role is not configured' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRoleSecond + Ensure = 'Present' + } - It 'Should return the state as absent' { - $result.Ensure | Should Be 'Present' + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -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 - $result.Name | Should Be $testParameters.Name - $result.Role | Should Be $testParameters.Role + Context 'When the system is not in the desired state and Ensure is set to Present' { + It 'Should return the state as false when two desired roles are not configured' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = @($mockSqlDatabaseRole,$mockSqlDatabaseRoleSecond) + Ensure = 'Present' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } - 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 not in the desired state and Ensure is set to Absent' { + It 'Should return the state as false when undesired roles are not configured' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginTwo + Database = $mockSqlDatabaseName + Role = @($mockSqlDatabaseRole,$mockSqlDatabaseRoleSecond) + Ensure = 'Absent' + } - Assert-VerifiableMocks - } + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Object | - Add-Member NoteProperty LoginType 'WindowsUser' -PassThru ) ) - 'John' = @( ( New-Object Object | - Add-Member NoteProperty LoginType 'WindowsUser' -PassThru ) ) - } - } -PassThru | - Add-Member ScriptProperty Databases { - return @{ - 'MyDatabase' = @( ( New-Object Object | - Add-Member ScriptProperty Users { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Object | - Add-Member ScriptMethod IsMember { - return $true - } -PassThru ) ) - 'John' = @( ( New-Object Object | - Add-Member ScriptMethod IsMember { - param( - [String] $Role - ) - if( $Role -eq 'MySecondRole' ) { - return $true - } else { - return $false - } - } -PassThru ) ) } - } -PassThru | - Add-Member ScriptProperty Roles { - return @{ - 'MyRole' = @( ( New-Object Object ) ) - 'MySecondRole' = @( ( New-Object Object ) ) - } - } -PassThru ) ) + Context 'When the system is in the desired state and Ensure is set to Present' { + It 'Should return the state as true when one desired role is correctly configured' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRole + Ensure = 'Present' } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - Context 'When the system is not in the desired state' { - It 'Should return that desired state as absent when adding one role' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginNameReturnsAbsent - Database = $databaseName - Role = $roleName + $result = Test-TargetResource @testParameters + $result | Should Be $true } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + Context 'When the system is in the desired state and Ensure is set to Present' { + It 'Should return the state as true when two desired role are correctly configured' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginTwo + Database = $mockSqlDatabaseName + Role = @($mockSqlDatabaseRole,$mockSqlDatabaseRoleSecond) + Ensure = 'Present' + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } } - It 'Should return that desired state as absent when adding two roles' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginNameReturnsAbsent - Database = $databaseName - Role = @( $roleName, $secondRoleName ) + Context 'When the system is in the desired state and Ensure is set to Absent' { + It 'Should return the state as true when two desired role are correctly configured' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRoleSecond + Ensure = 'Absent' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context } + } - $result = Test-TargetResource @testParameters - $result | Should Be $false + Assert-VerifiableMocks + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Describe "MSFT_xSQLServerDatabaseRole\Set-TargetResource" -Tag 'Set'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + Mock -CommandName New-Object -MockWith $mockNewObjectUser -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Verifiable } - } - Context 'When the system is in the desired state' { - It 'Should return that desired state as present when adding one role' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginName - Database = $databaseName - Role = $roleName - } + Context 'When the system is not in the desired state, Ensure is set to Present and Login does not exist' { + It 'Should Not Throw when Ensure parameter is set to Present' { + $mockExpectedForAddMemberMethod = 'MyRole' + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLogin + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRole + Ensure = 'Present' + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.User' { + Assert-MockCalled New-Object -Exactly -Times 1 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Scope Context + } } - It 'Should return that desired state as present when adding two roles' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginName - Database = $databaseName - Role = @( $roleName, $secondRoleName ) - } + Context 'When the system is not in the desired state, Ensure is set to Present and Login does not exist' { + It 'Should Throw the correct error when Ensure parameter is set to Present' { + $mockInvalidOperationForCreateMethod = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLogin + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRoleSecond + Ensure = 'Present' + } - $result = Test-TargetResource @testParameters - $result | Should Be $true + $throwInvalidOperation = ('Failed adding the login John as a user of the database AdventureWorks, on ' + ` + 'the instance localhost\MSSQLSERVER. InnerException: Exception calling "Create" ' + ` + 'with "0" argument(s): "Mock Create Method was called with invalid operation."') - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } - Assert-VerifiableMocks - } + It 'Should call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.User' { + Assert-MockCalled New-Object -Exactly -Times 1 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Scope Context + } + } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock -CommandName Connect-SQL -MockWith { - return New-Object Object | - Add-Member ScriptProperty Logins { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Object | - Add-Member NoteProperty LoginType 'WindowsUser' -PassThru ) ) - 'John' = @( ( New-Object Object | - Add-Member NoteProperty LoginType 'WindowsUser' -PassThru ) ) + Context 'When the system is not in the desired state, Ensure is set to Present and Login already exist' { + It 'Should Not Throw when Ensure parameter is set to Present' { + $mockExpectedForAddMemberMethod = 'MySecondRole' + $mockSqlServerLogin = $mockSqlServerLoginOne + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLogin + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRoleSecond + Ensure = 'Present' } - } -PassThru | - Add-Member ScriptProperty Databases { - return @{ - 'MyDatabase' = @( ( New-Object Object | - Add-Member ScriptProperty Users { - return @{ - 'COMPANY\Stacy' = @( ( New-Object Object | - Add-Member ScriptMethod IsMember { - return $true - } -PassThru ) ) - 'John' = @( ( New-Object Object | - Add-Member ScriptMethod IsMember { - param( - [String] $Role - ) - if( $Role -eq 'MySecondRole' ) { - return $true - } else { - return $false - } - } -PassThru ) ) } - } -PassThru | - Add-Member ScriptProperty Roles { - return @{ - 'MyRole' = @( ( New-Object Object | - Add-Member ScriptMethod AddMember { - param( - [String] $Name - ) - } -PassThru ) ) - 'MySecondRole' = @( ( New-Object Object | - Add-Member ScriptMethod AddMember { - param( - [String] $Name - ) - } -PassThru ) ) - } - } -PassThru ) ) - } - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginName - Database = $databaseName - Role = $roleName + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + + It 'Should not call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.User' { + Assert-MockCalled New-Object -Exactly -Times 0 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Scope Context + } } - It 'Should not throw when login is not member of the group' { - { Set-TargetResource @testParameters } | Should Not Throw + Context 'When the system is not in the desired state, Ensure is set to Present and Login already exist' { + It 'Should Throw the correct error when Ensure parameter is set to Present' { + $mockInvalidOperationForAddMemberMethod = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginOne + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRoleSecond + Ensure = 'Present' + } + + $throwInvalidOperation = ('Failed adding the login CONTOSO\KingJulian to the role MySecondRole on ' + ` + 'the database AdventureWorks, on the instance localhost\MSSQLSERVER. ' + ` + 'InnerException: Exception calling "AddMember" with "1" argument(s): ' + ` + '"Mock AddMember Method was called with invalid operation."') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } - Assert-MockCalled Connect-SQL -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + It 'Should not call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.User' { + Assert-MockCalled New-Object -Exactly -Times 0 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Scope Context + } } - } - Context 'When the system is in the desired state' { - # Mock the return value from the Get-method, because Test-method is ran at the end of the Set-method to validate that the Set (in this case) was successful. - Mock -CommandName Get-TargetResource -MockWith { - @{ - Ensure = 'Present' + Context 'When the system is not in the desired state, Ensure is set to Absent' { + It 'Should not throw the correct error when Ensure parameter is set to Absent' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginTwo + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRole + Ensure = 'Absent' + } + + { Set-TargetResource @testParameters } | Should Not Throw + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context } - } -ModuleName $script:DSCResourceName -Verifiable - $testParameters = $defaultParameters - $testParameters += @{ - Name = $loginNameReturnsAbsent - Database = $databaseName - Role = $roleName + It 'Should not call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.User' { + Assert-MockCalled New-Object -Exactly -Times 0 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Scope Context + } } - It 'Should not throw when login is already member of the group' { - { Set-TargetResource @testParameters } | Should Not Throw + $mockInvalidOperationForDropMemberMethod = $true + + Context 'When the system is not in the desired state, Ensure is set to Absent' { + It 'Should not throw the correct error when Ensure parameter is set to Absent' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Name = $mockSqlServerLoginTwo + Database = $mockSqlDatabaseName + Role = $mockSqlDatabaseRole + Ensure = 'Absent' + } + + $throwInvalidOperation = ('Failed removing the login CONTOSO\SQLAdmin from the role MyRole on ' + ` + 'the database AdventureWorks, on the instance localhost\MSSQLSERVER. ' + ` + 'InnerException: Exception calling "DropMember" with "1" argument(s): ' + ` + '"Mock DropMember Method was called with invalid operation."') - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + + It 'Should not call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.User' { + Assert-MockCalled New-Object -Exactly -Times 0 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.User' + } -Scope Context + } } - } - Assert-VerifiableMocks + Assert-VerifiableMocks + } } } finally { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xSQLServerEndpoint.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerEndpoint.Tests.ps1 new file mode 100644 index 000000000..6c428eebd --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerEndpoint.Tests.ps1 @@ -0,0 +1,657 @@ +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerEndpoint' + +#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 { + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockNodeName = 'localhost' + $mockInstanceName = 'INSTANCE1' + $mockPrincipal = 'COMPANY\SqlServiceAcct' + $mockOtherPrincipal = 'COMPANY\OtherAcct' + $mockEndpointName = 'DefaultEndpointMirror' + $mockEndpointType = 'DatabaseMirroring' + $mockEndpointListenerPort = 5022 + $mockEndpointListenerIpAddress = '0.0.0.0' # 0.0.0.0 means listen on all IP addresses. + + $mockOtherEndpointName = 'UnknownEndpoint' + $mockOtherEndpointType = 'UnknownType' + $mockOtherEndpointListenerPort = 9001 + $mockOtherEndpointListenerIpAddress = '192.168.0.20' + + $script:mockMethodAlterRan = $false + $script:mockMethodCreateRan = $false + $script:mockMethodDropRan = $false + $script:mockMethodStartRan = $false + + $mockDynamicEndpointName = $mockEndpointName + $mockDynamicEndpointType = $mockEndpointType + $mockDynamicEndpointListenerPort = $mockEndpointListenerPort + $mockDynamicEndpointListenerIpAddress = $mockEndpointListenerIpAddress + + $mockEndpointObject = { + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDynamicEndpointName -PassThru | + Add-Member -MemberType NoteProperty -Name 'EndpointType' -Value $mockDynamicEndpointType -PassThru | + Add-Member -MemberType NoteProperty -Name 'ProtocolType' -Value $null -PassThru | + Add-Member -MemberType ScriptProperty -Name 'Protocol' { + return New-Object Object | + Add-Member -MemberType ScriptProperty -Name 'Tcp' { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ListenerPort' -Value $mockDynamicEndpointListenerPort -PassThru | + Add-Member -MemberType NoteProperty -Name 'ListenerIPAddress' -Value $mockDynamicEndpointListenerIpAddress -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptProperty -Name 'Payload' { + return New-Object Object | + Add-Member -MemberType ScriptProperty -Name 'DatabaseMirroring' { + return New-Object Object | + Add-Member -MemberType NoteProperty -Name 'ServerMirroringRole' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'EndpointEncryption' -Value $null -PassThru | + Add-Member -MemberType NoteProperty -Name 'EndpointEncryptionAlgorithm' -Value $null -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' { + $script:mockMethodAlterRan = $true + + if ( $this.Name -ne $mockExpectedNameWhenCallingMethod ) + { + throw "Called mocked Alter() method on and endpoint with wrong name. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedNameWhenCallingMethod, $this.Name + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Drop' { + $script:mockMethodDropRan = $true + + if ( $this.Name -ne $mockExpectedNameWhenCallingMethod ) + { + throw "Called mocked Drop() method on and endpoint with wrong name. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedNameWhenCallingMethod, $this.Name + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Start' { + $script:mockMethodStartRan = $true + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Create' { + $script:mockMethodCreateRan = $true + + if ( $this.Name -ne $mockExpectedNameWhenCallingMethod ) + { + throw "Called mocked Create() method on and endpoint with wrong name. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedNameWhenCallingMethod, $this.Name + } + } -PassThru -Force + } + + $mockConnectSql = { + return New-Object Object | + Add-Member ScriptProperty Endpoints { + return @( + @{ + # This executes the script block $mockEndpointObject and returns a mocked Microsoft.SqlServer.Management.Smo.Endpoint + $mockDynamicEndpointName = & $mockEndpointObject + } + ) + } -PassThru -Force + } + + $mockNewObjectEndPoint = { + # This executes the script block $mockEndpointObject and returns a mocked Microsoft.SqlServer.Management.Smo.Endpoint + return & $mockEndpointObject + } + + $mockNewObjectEndPoint_ParameterFilter = { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Endpoint' + } + + $defaultParameters = @{ + SQLInstanceName = $mockInstanceName + SQLServer = $mockNodeName + EndpointName = $mockEndpointName + } + + Describe 'MSFT_xSQLServerEndpoint\Get-TargetResource' -Tag 'Get' { + BeforeEach { + $testParameters = $defaultParameters + + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + } + + # Make sure the mock does not return the correct endpoint + $mockDynamicEndpointName = $mockOtherEndpointName + + Context 'When the system is not in the desired state' { + It 'Should return the desired state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + } + + It 'Should not return any values in the properties for the endpoint' { + $result = Get-TargetResource @testParameters + $result.EndpointName | Should Be '' + $result.Port | Should Be '' + $result.IpAddress | Should Be '' + } + + It 'Should call the mock function Connect-SQL' { + Get-TargetResource @testParameters | Out-Null + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + # Make sure the mock do return the correct endpoint + $mockDynamicEndpointName = $mockEndpointName + + Context 'When the system is in the desired state' { + It 'Should return the desired state as present' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.EndpointName | Should Be $testParameters.EndpointName + $result.Port | Should Be $mockEndpointListenerPort + $result.IpAddress | Should Be $mockEndpointListenerIpAddress + } + + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Make sure the mock return the endpoint with wrong endpoint type + $mockDynamicEndpointType = $mockOtherEndpointType + + Context 'When endpoint exist but with wrong endpoint type' { + It 'Should throw the correct error' { + { Get-TargetResource @testParameters } | Should Throw 'Endpoint ''DefaultEndpointMirror'' does exist, but it is not of type ''DatabaseMirroring''.' + } + } + + # Make sure the mock return the endpoint with correct endpoint type + $mockDynamicEndpointType = $mockEndpointType + } + + Context 'When Connect-SQL returns nothing' { + It 'Should throw the correct error' { + Mock -CommandName Connect-SQL -MockWith { + return $null + } + + { Get-TargetResource @testParameters } | Should Throw 'Was unable to connect to the instance ''localhost\INSTANCE1''' + } + } + + Assert-VerifiableMocks + } + + Describe 'MSFT_xSQLServerEndpoint\Test-TargetResource' -Tag 'Test' { + BeforeEach { + $testParameters = $defaultParameters.Clone() + + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + } + + Context 'When the system is not in the desired state' { + # Make sure the mock does not return the correct endpoint + $mockDynamicEndpointName = $mockOtherEndpointName + + It 'Should return that desired state is absent when wanted desired state is to be Present (using default values)' { + $testParameters.Add('Ensure', 'Present') + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return that desired state is absent when wanted desired state is to be Present (setting all parameters)' { + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('Port', $mockEndpointListenerPort) + $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Make sure the mock do return the correct endpoint + $mockDynamicEndpointName = $mockEndpointName + + It 'Should return that desired state is absent when wanted desired state is to be Absent' { + $testParameters.Add('Ensure', 'Absent') + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener port + $mockDynamicEndpointName = $mockEndpointName + $mockDynamicEndpointListenerPort = $mockOtherEndpointListenerPort + + Context 'When listener port is not in desired state' { + It 'Should return that desired state is absent' { + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('Port', $mockEndpointListenerPort) + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + # Make sure the mock do return the correct endpoint listener port + $mockDynamicEndpointListenerPort = $mockEndpointListenerPort + + # Make sure the mock do return the correct endpoint, but does not return the correct endpoint listener IP address + $mockDynamicEndpointName = $mockEndpointName + $mockDynamicEndpointListenerIpAddress = $mockOtherEndpointListenerIpAddress + + Context 'When listener IP address is not in desired state' { + It 'Should return that desired state is absent' { + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + # Make sure the mock do return the correct endpoint listener IP address + $mockDynamicEndpointListenerIpAddress = $mockEndpointListenerIpAddress + } + + Context 'When the system is in the desired state' { + # Make sure the mock do return the correct endpoint + $mockDynamicEndpointName = $mockEndpointName + + It 'Should return that desired state is present when wanted desired state is to be Present (using default values)' { + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Make sure the mock does not return the correct endpoint + $mockDynamicEndpointName = $mockOtherEndpointName + + It 'Should return that desired state is present when wanted desired state is to be Absent' { + $testParameters.Add('Ensure', 'Absent') + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe 'MSFT_xSQLServerEndpoint\Set-TargetResource' -Tag 'Set' { + BeforeEach { + $testParameters = $defaultParameters.Clone() + + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + Mock -CommandName New-Object -MockWith $mockNewObjectEndPoint -ParameterFilter $mockNewObjectEndPoint_ParameterFilter -Verifiable + } + + Context 'When the system is not in the desired state' { + # Make sure the mock do return the correct endpoint + $mockDynamicEndpointName = $mockEndpointName + + # Set all method call tests variables to $false + $script:mockMethodCreateRan = $false + $script:mockMethodStartRan = $false + $script:mockMethodAlterRan = $false + $script:mockMethodDropRan = $false + + # Set what the expected endpoint name should be when Create() method is called. + $mockExpectedNameWhenCallingMethod = $mockEndpointName + + It 'Should call the the method Create when desired state is to be Present (using default values)' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } -Verifiable + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodCreateRan | Should Be $true + $script:mockMethodStartRan | Should Be $true + $script:mockMethodAlterRan | Should Be $false + $script:mockMethodDropRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Set all method call tests variables to $false + $script:mockMethodCreateRan = $false + $script:mockMethodStartRan = $false + $script:mockMethodAlterRan = $false + $script:mockMethodDropRan = $false + + # Set what the expected endpoint name should be when Create() method is called. + $mockExpectedNameWhenCallingMethod = $mockEndpointName + + It 'Should call the the method Create when desired state is to be Present (setting all parameters)' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } -Verifiable + + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('Port', $mockEndpointListenerPort) + $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodCreateRan | Should Be $true + $script:mockMethodStartRan | Should Be $true + $script:mockMethodAlterRan | Should Be $false + $script:mockMethodDropRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Set all method call tests variables to $false + $script:mockMethodCreateRan = $false + $script:mockMethodStartRan = $false + $script:mockMethodAlterRan = $false + $script:mockMethodDropRan = $false + + # Set what the expected endpoint name should be when Drop() method is called. + $mockExpectedNameWhenCallingMethod = $mockEndpointName + + It 'Should call the the method Drop when desired state is to be Absent' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + } + } -Verifiable + + $testParameters.Add('Ensure', 'Absent') + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodCreateRan | Should Be $false + $script:mockMethodStartRan | Should Be $false + $script:mockMethodAlterRan | Should Be $false + $script:mockMethodDropRan | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Set all method call tests variables to $false + $script:mockMethodCreateRan = $false + $script:mockMethodStartRan = $false + $script:mockMethodAlterRan = $false + $script:mockMethodDropRan = $false + + # Set what the expected endpoint name should be when Alter() method is called. + $mockExpectedNameWhenCallingMethod = $mockEndpointName + + It 'Should not call Alter method when listener port is not in desired state' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Port = $mockEndpointListenerPort + IpAddress = $mockEndpointListenerIpAddress + } + } -Verifiable + + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('Port', $mockOtherEndpointListenerPort) + $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodCreateRan | Should Be $false + $script:mockMethodStartRan | Should Be $false + $script:mockMethodAlterRan | Should Be $true + $script:mockMethodDropRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Set all method call tests variables to $false + $script:mockMethodCreateRan = $false + $script:mockMethodStartRan = $false + $script:mockMethodAlterRan = $false + $script:mockMethodDropRan = $false + + # Set what the expected endpoint name should be when Alter() method is called. + $mockExpectedNameWhenCallingMethod = $mockEndpointName + + It 'Should not call Alter method when listener IP address is not in desired state' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Port = $mockEndpointListenerPort + IpAddress = $mockEndpointListenerIpAddress + } + } -Verifiable + + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('Port', $mockEndpointListenerPort) + $testParameters.Add('IpAddress', $mockOtherEndpointListenerIpAddress) + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodCreateRan | Should Be $false + $script:mockMethodStartRan | Should Be $false + $script:mockMethodAlterRan | Should Be $true + $script:mockMethodDropRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Make sure the mock does not return the correct endpoint + $mockDynamicEndpointName = $mockOtherEndpointName + + Context 'When endpoint is missing when Ensure is set to Present' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Port = $mockEndpointListenerPort + IpAddress = $mockEndpointListenerIpAddress + } + } -Verifiable + + { Set-TargetResource @testParameters } | Should Throw 'Endpoint ''DefaultEndpointMirror'' does not exist' + } + } + + Context 'When endpoint is missing when Ensure is set to Absent' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Port = $mockEndpointListenerPort + IpAddress = $mockEndpointListenerIpAddress + } + } -Verifiable + + $testParameters.Add('Ensure', 'Absent') + + { Set-TargetResource @testParameters } | Should Throw 'Endpoint ''DefaultEndpointMirror'' does not exist' + } + } + + Context 'When Connect-SQL returns nothing' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith {} -Verifiable + Mock -CommandName Connect-SQL -MockWith { + return $null + } + + { Set-TargetResource @testParameters } | Should Throw 'Was unable to connect to the instance ''localhost\INSTANCE1''' + } + } + } + + Context 'When the system is in the desired state' { + # Make sure the mock do return the correct endpoint + $mockDynamicEndpointName = $mockEndpointName + $mockDynamicEndpointListenerPort = $mockEndpointListenerPort + $mockDynamicEndpointListenerIpAddress = $mockEndpointListenerIpAddress + + # Set all method call tests variables to $false + $script:mockMethodCreateRan = $false + $script:mockMethodStartRan = $false + $script:mockMethodAlterRan = $false + $script:mockMethodDropRan = $false + + It 'Should not call any methods when desired state is already Present' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Port = $mockEndpointListenerPort + IpAddress = $mockEndpointListenerIpAddress + } + } -Verifiable + + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('Port', $mockEndpointListenerPort) + $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodCreateRan | Should Be $false + $script:mockMethodStartRan | Should Be $false + $script:mockMethodAlterRan | Should Be $false + $script:mockMethodDropRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + # Make sure the mock does not return the correct endpoint + $mockDynamicEndpointName = $mockOtherEndpointName + + # Set all method call tests variables to $false + $script:mockMethodCreateRan = $false + $script:mockMethodStartRan = $false + $script:mockMethodAlterRan = $false + $script:mockMethodDropRan = $false + + It 'Should not call any methods when desired state is already Absent' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } -Verifiable + + $testParameters.Add('Ensure', 'Absent') + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodCreateRan | Should Be $false + $script:mockMethodStartRan | Should Be $false + $script:mockMethodAlterRan | Should Be $false + $script:mockMethodDropRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + # Make sure the mock do return the correct endpoint + $mockDynamicEndpointName = $mockEndpointName + $mockExpectedNameWhenCallingMethod = $mockOtherEndpointName + + Context 'Testing mocks' { + Context 'When mocked Create() method is called with the wrong endpoint name' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } -Verifiable + + { Set-TargetResource @testParameters } | Should Throw 'Exception calling "Create" with "0" argument(s): "Called mocked Create() method on and endpoint with wrong name. Expected ''UnknownEndpoint''. But was ''DefaultEndpointMirror''."' + } + } + + Context 'When mocked Drop() method is called with the wrong endpoint name' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + } + } -Verifiable + + $testParameters.Add('Ensure', 'Absent') + + { Set-TargetResource @testParameters } | Should Throw 'Exception calling "Drop" with "0" argument(s): "Called mocked Drop() method on and endpoint with wrong name. Expected ''UnknownEndpoint''. But was ''DefaultEndpointMirror''."' + } + } + + Context 'When mocked Alter() method is called with the wrong endpoint name' { + It 'Should throw the correct error' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Present' + Port = $mockEndpointListenerPort + IpAddress = $mockEndpointListenerIpAddress + } + } -Verifiable + + $testParameters.Add('Ensure', 'Present') + $testParameters.Add('Port', $mockOtherEndpointListenerPort) + $testParameters.Add('IpAddress', $mockEndpointListenerIpAddress) + + { Set-TargetResource @testParameters } | Should Throw 'Exception calling "Alter" with "0" argument(s): "Called mocked Alter() method on and endpoint with wrong name. Expected ''UnknownEndpoint''. But was ''DefaultEndpointMirror''."' + } + } + } + + Assert-VerifiableMocks + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 index f98ecf322..373a011f1 100644 --- a/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerEndpointPermission.Tests.ps1 @@ -3,10 +3,10 @@ $script:DSCResourceName = 'MSFT_xSQLServerEndpointPermission' #region HEADER -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +# 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'))) ) + (-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\')) } @@ -16,413 +16,329 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion HEADER +function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + # Begin Testing try { - #region Pester Test Initialization + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockNodeName = 'localhost' + $mockInstanceName = 'SQL2016' + $mockPrincipal = 'COMPANY\SqlServiceAcct' + $mockOtherPrincipal = 'COMPANY\OtherAcct' + $mockEndpointName = 'DefaultEndpointMirror' + + $mockDynamicEndpointName = $mockEndpointName + + $script:mockMethodGrantRan = $false + $script:mockMethodRevokeRan = $false + + $mockConnectSql = { + return New-Object Object | + Add-Member -MemberType ScriptProperty -Name 'Endpoints' { + return @( + @{ + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + $mockDynamicEndpointName = New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockEndpointName -PassThru | + Add-Member -MemberType ScriptMethod -Name 'EnumObjectPermissions' { + param($permissionSet) + return @( + (New-Object Object | + Add-Member -MemberType NoteProperty Grantee $mockDynamicPrincipal -PassThru | + Add-Member -MemberType NoteProperty PermissionState 'Grant' -PassThru + ) + ) + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Grant' { + param( + $permissionSet, + $mockPrincipal + ) + + $script:mockMethodGrantRan = $true + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Revoke' { + param( + $permissionSet, + $mockPrincipal + ) + + $script:mockMethodRevokeRan = $true + } -PassThru -Force + } + ) + } -PassThru -Force + } - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + $defaultParameters = @{ + InstanceName = $mockInstanceName + NodeName = $mockNodeName + Name = $mockEndpointName + Principal = $mockPrincipal + } - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $principal = 'COMPANY\SqlServiceAcct' - $otherPrincipal = 'COMPANY\OtherAcct' - $endpointName = 'DefaultEndpointMirror' - - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Name = $endpointName - Principal = $principal - } + Describe 'MSFT_xSQLServerEndpointPermission\Get-TargetResource' -Tag 'Get' { + BeforeEach { + $testParameters = $defaultParameters - #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' + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable } - 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 - } + $mockDynamicPrincipal = $mockOtherPrincipal - It 'Should not return any permissions' { - $result.Permission | Should Be '' - } + Context 'When the system is not in the desired state' { + It 'Should return the desired state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } - 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' - } + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $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 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 not return any permissions' { + $result = Get-TargetResource @testParameters + $result.Permission | Should Be '' + } - It 'Should return the permissions passed as parameter' { - $result.Permission | Should Be 'CONNECT' - } + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } - It 'Should call the mock function Get-SQLAlwaysOnEndpoint' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } - } + $mockDynamicEndpointName = 'UnknownEndPoint' - Assert-VerifiableMocks - } + Context 'When endpoint is missing' { + It 'Should throw the correct error message' { + { Get-TargetResource @testParameters } | Should Throw 'Got unexpected result from Get-TargetResource. No change is made. InnerException: Endpoint 'DefaultEndpointMirror' does not exist' - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } - 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 - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + $mockDynamicEndpointName = $mockEndpointName } - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' - } + $mockDynamicPrincipal = $mockPrincipal - 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 - ) - ) - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } + Context 'When the system is in the desired state' { + It 'Should return the desired state as present' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + } - 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 the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $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 = Get-TargetResource @testParameters + $result.Permission | Should Be '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-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - It 'Should return that desired state is present when wanted desired state is to be Absent' { + Assert-VerifiableMocks + } + + Describe 'MSFT_xSQLServerEndpointPermission\Test-TargetResource' -Tag 'Test' { + BeforeEach { $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 -Force - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable } - } - Assert-VerifiableMocks - } + Context 'When the system is not in the desired state' { + $mockDynamicPrincipal = $mockOtherPrincipal - 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 absent when wanted desired state is to be Present' { + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It } - 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 - ) - 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 2 -ModuleName $script:DSCResourceName -Scope It + $mockDynamicPrincipal = $mockPrincipal + + It 'Should return that desired state is absent when wanted desired state is to be Absent' { + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - It 'Should call the the method Revoke when desired state is to be Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' + Context 'When the system is in the desired state' { + $mockDynamicPrincipal = $mockPrincipal + + It 'Should return that desired state is present when wanted desired state is to be Present' { + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It } - 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 - ) - return - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - { Set-TargetResource @testParameters } | Should Not Throw - - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + $mockDynamicPrincipal = $mockOtherPrincipal + + It 'Should return that desired state is present when wanted desired state is to be Absent' { + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } + + Assert-VerifiableMocks } - Context 'When the system is in the desired state' { - It 'Should not throw error when desired state is already Present' { + Describe 'MSFT_xSQLServerEndpointPermission\Set-TargetResource' -Tag 'Set' { + BeforeEach { $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - Permission = 'CONNECT' + + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + } + + Context 'When the system is not in the desired state' { + $mockDynamicPrincipal = $mockOtherPrincipal + $script:mockMethodGrantRan = $false + $script:mockMethodRevokeRan = $false + + It 'Should call the the method Grant when desired state is to be Present' { + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' + } + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodGrantRan | Should Be $true + $script:mockMethodRevokeRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It } - 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 + $mockDynamicPrincipal = $mockPrincipal + $script:mockMethodGrantRan = $false + $script:mockMethodRevokeRan = $false + + It 'Should call the the method Revoke when desired state is to be Absent' { + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' + } + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It + } + + $mockDynamicEndpointName = 'UnknownEndPoint' + + Context 'When endpoint is missing' { + It 'Should throw the correct error message' { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Ensure = 'Absent' + } + } -Verifiable + + { Set-TargetResource @testParameters } | Should Throw 'Endpoint 'DefaultEndpointMirror' does not exist' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + $mockDynamicEndpointName = $mockEndpointName } - It 'Should not throw error when desired state is already Absent' { - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - Permission = 'CONNECT' + Context 'When the system is in the desired state' { + $mockDynamicPrincipal = $mockPrincipal + $script:mockMethodGrantRan = $false + $script:mockMethodRevokeRan = $false + + It 'Should not call Grant() or Revoke() method when desired state is already Present' { + $testParameters += @{ + Ensure = 'Present' + Permission = 'CONNECT' + } + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It } - 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 - ) - 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 + $mockDynamicPrincipal = $mockOtherPrincipal + $script:mockMethodGrantRan = $false + $script:mockMethodRevokeRan = $false + + It 'Should not call Grant() or Revoke() method when desired state is already Absent' { + $testParameters += @{ + Ensure = 'Absent' + Permission = 'CONNECT' + } + + { Set-TargetResource @testParameters } | Should Not Throw + $script:mockMethodGrantRan | Should Be $false + $script:mockMethodRevokeRan | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } + + Assert-VerifiableMocks } - Assert-VerifiableMocks } } finally { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xSQLServerEndpointState.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerEndpointState.Tests.ps1 index 1f44eaf80..de10f0164 100644 --- a/Tests/Unit/MSFT_xSQLServerEndpointState.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerEndpointState.Tests.ps1 @@ -1,10 +1,10 @@ $script:DSCModuleName = 'xSQLServer' -$script:DSCResourceName = 'MSFT_xSQLServerEndPointState' +$script:DSCResourceName = 'MSFT_xSQLServerEndpointState' #region HEADER -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +# 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'))) ) { @@ -16,355 +16,325 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion HEADER +function Invoke-TestSetup { + # Loading stub cmdlets + Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force -Global +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + # Begin Testing try { - #region Pester Test Initialization + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockNodeName = 'localhost' + $mockInstanceName = 'INSTANCE1' + $mockEndpointName = 'DefaultEndpointMirror' + $mockEndpointStateStarted = 'Started' + $mockEndpointStateStopped = 'Stopped' + + $mockOtherEndpointName = 'OtherEndpoint' + + $mockDynamicEndpointName = $mockEndpointName + $mockDynamicEndpointState = $mockEndpointStateStarted + + $mockConnectSql = { + return New-Object Object | + Add-Member -MemberType ScriptProperty -Name 'Endpoints' { + return @( + @{ + # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint + $mockDynamicEndpointName = New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockDynamicEndpointName -PassThru | + Add-Member -MemberType NoteProperty -Name 'EndpointState' -Value $mockDynamicEndpointState -PassThru -Force + } + ) + } -PassThru -Force + } - # Loading stub cmdlets - Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SQLPSStub.psm1') -Force + $defaultParameters = @{ + InstanceName = $mockInstanceName + NodeName = $mockNodeName + Name = $mockEndpointName + } - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $endpointName = 'DefaultEndpointMirror' + #endregion Pester Test Initialization - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Name = $endpointName - } + Describe 'MSFT_xSQLServerEndpointState\Get-TargetResource' -Tag Get { + BeforeEach { + $testParameters = $defaultParameters.Clone() - #endregion Pester Test Initialization + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - $testParameters = $defaultParameters + $mockDynamicEndpointName = $mockEndpointName - Context 'When the system is not in the desired state' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Stopped' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable + Context 'When the system is not in the desired state' { + $mockDynamicEndpointState = $mockEndpointStateStopped - $result = Get-TargetResource @testParameters + Context 'When desired state should be Started, but the current state is Stopped' { + It 'Should not return the state as Started' { + $result = Get-TargetResource @testParameters + $result.State | Should Not Be $mockEndpointStateStarted + } - It 'Should not return the same value as expected state Started' { - $result.State | Should Not Be 'Started' - $result.State | Should Be 'Stopped' - } + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + } - It 'Should return the same values as passed as parameters when expected state is Started' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when expected state is Started' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } - - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Started' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - - It 'Should not return the same value as expected state Stopped' { - $result.State | Should Not Be 'Stopped' - $result.State | Should Be 'Started' - } + $mockDynamicEndpointState = $mockEndpointStateStarted + + Context 'When desired state should be Stopped, but the current state is Started' { + It 'Should not return the state as Stopped' { + $result = Get-TargetResource @testParameters + $result.State | Should Not Be $mockEndpointStateStopped + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } - It 'Should return the same values as passed as parameters when expected state is Stopped' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - } + $mockDynamicEndpointName = $mockOtherEndpointName - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when expected state is Stopped' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope Context - } - } - - Context 'When the system is in the desired state' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Started' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable - - $result = Get-TargetResource @testParameters - - It 'Should return the same value as expected state Started' { - $result.State | Should Not Be 'Stopped' - $result.State | Should Be 'Started' - } + Context 'When endpoint is missing' { + It 'Should throw the correct error message' { + { Get-TargetResource @testParameters } | Should Throw 'Unexpected result when trying to verify existence of endpoint 'DefaultEndpointMirror'. InnerException: Endpoint 'DefaultEndpointMirror' does not exist' - It 'Should return the same values as passed as parameters when expected state is Started' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } - It 'Should call the mock function Get-SQLPSInstance when expected state is Started' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context + $mockDynamicEndpointName = $mockEndpointName } - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Stopped' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable + Context 'When the system is in the desired state' { + $mockDynamicEndpointState = $mockEndpointStateStarted - $result = Get-TargetResource @testParameters + Context 'When desired state is Started' { + It 'Should return the state as Started' { + $result = Get-TargetResource @testParameters + $result.State | Should Be $mockEndpointStateStarted + } - It 'Should return the same value as expected state Stopped' { - $result.State | Should Not Be 'Started' - $result.State | Should Be 'Stopped' - } + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + } - It 'Should return the same values as passed as parameters when expected state is Stopped' { - $result.NodeName | Should Be $testParameters.NodeName - $result.InstanceName | Should Be $testParameters.InstanceName - $result.Name | Should Be $testParameters.Name - } + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } - It 'Should call the mock function Get-SQLPSInstance when expected state is Stopped' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope Context + $mockDynamicEndpointState = $mockEndpointStateStopped + + Context 'When desired state is Stopped' { + It 'Should return the state as Stopped' { + $result = Get-TargetResource @testParameters + $result.State | Should Be $mockEndpointStateStopped + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.NodeName | Should Be $testParameters.NodeName + $result.InstanceName | Should Be $testParameters.InstanceName + $result.Name | Should Be $testParameters.Name + } + + It 'Should call the mock function Connect-SQL' { + $result = Get-TargetResource @testParameters + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } } + + Assert-VerifiableMocks } - Assert-VerifiableMocks - } - - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Context 'When the system is not in the desired state' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Stopped' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state as absent when desired state is Started' { - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Started' - } + Describe 'MSFT_xSQLServerEndpointState\Test-TargetResource' -Tag Test { + BeforeEach { + $testParameters = $defaultParameters.Clone() - $result = Test-TargetResource @testParameters - $result | Should Be $false + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable } - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state of Started is absent' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Context 'When the system is not in the desired state' { + $mockDynamicEndpointState = $mockEndpointStateStopped + + Context 'When desired state should be Started, but the current state is Stopped' { + It 'Should return that desired state as absent' { + $testParameters.Add('State', $mockEndpointStateStarted) - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Started' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $false - It 'Should return that desired state as absent when desired state is Stopped' { - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Stopped' + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - $result = Test-TargetResource @testParameters - $result | Should Be $false - } + $mockDynamicEndpointState = $mockEndpointStateStarted + + Context 'When desired state should be Stopped, but the current state is Started' { + It 'Should return that desired state as absent' { + $testParameters.Add('State', $mockEndpointStateStopped) - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state of Started is absent' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope Context + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } } - } - Context 'When the system is in the desired state' { - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Started' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should return that desired state as present when desired state is Started' { - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Started' + Context 'When the system is in the desired state' { + $mockDynamicEndpointState = $mockEndpointStateStarted + + Context 'When desired state should be Started, and the current state is Started' { + It 'Should return that desired state as absent' { + $testParameters.Add('State', $mockEndpointStateStarted) + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - $result = Test-TargetResource @testParameters - $result | Should Be $true - } + $mockDynamicEndpointState = $mockEndpointStateStopped - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state of Started is present' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope Context - } + Context 'When desired state should be Stopped, and the current state is Stopped' { + It 'Should return that desired state as absent' { + $testParameters.Add('State', $mockEndpointStateStopped) - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Stopped' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable + $result = Test-TargetResource @testParameters + $result | Should Be $true - It 'Should return that desired state as present when desired state is Stopped' { - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Stopped' + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - $result = Test-TargetResource @testParameters - $result | Should Be $true - } + Context 'When Get-TargetResource returns nothing' { + It 'Should throw the correct error message' { + Mock -CommandName Get-TargetResource -MockWith { + return $null + } -Verifiable - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state of Stopped is present' { - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope Context + { Test-TargetResource @testParameters } | Should Throw 'Got unexpected result from Get-TargetResource. No change is made.' + + Assert-MockCalled Connect-SQL -Exactly -Times 0 -Scope It + } + } } - } - Assert-VerifiableMocks - } + Assert-VerifiableMocks + } - Describe "$($script:DSCResourceName)\Set-TargetResource" { - Mock Set-SqlHADREndpoint -MockWith {} -ModuleName $script:DSCResourceName -Verifiable + Describe 'MSFT_xSQLServerEndpointState\Set-TargetResource' -Tag Set { + BeforeEach { + $testParameters = $defaultParameters.Clone() - Context 'When the system is not in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Stopped' + Mock -CommandName Connect-SQL -MockWith $mockConnectSql -Verifiable + Mock Set-SqlHADREndpoint -Verifiable } - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Started' -PassThru | # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - Add-Member ScriptProperty Protocol { - return New-Object Object | - Add-Member ScriptProperty Tcp { - return New-Object Object | - Add-Member ScriptProperty ListenerIPAddress { - return New-Object Object | - Add-Member NoteProperty IPAddressToString '10.0.0.1' -PassThru - } -PassThru - } -PassThru - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should not throw an error when desired state is not equal to Stopped' { - { Set-TargetResource @testParameters } | Should Not Throw - } + Context 'When the system is not in the desired state' { + $mockDynamicEndpointState = $mockEndpointStateStopped - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state is not equal to Stopped' { - Set-TargetResource @testParameters - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It - } + Context 'When desired state should be Started, but the current state is Stopped' { + It 'Should call the mock function Set-SqlHADREndpoint to set the state to Started' { + $testParameters.Add('State', $mockEndpointStateStarted) - It 'Should call the mock function Set-SqlHADREndpoint when desired state is not equal to Stopped' { - Set-TargetResource @testParameters - Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Set-TargetResource @testParameters - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Started' - } + Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 1 -Scope It + } + } - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Stopped' -PassThru | # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - Add-Member ScriptProperty Protocol { - return New-Object Object | - Add-Member ScriptProperty Tcp { - return New-Object Object | - Add-Member ScriptProperty ListenerIPAddress { - return New-Object Object | - Add-Member NoteProperty IPAddressToString '10.0.0.1' -PassThru - } -PassThru - } -PassThru - } -PassThru -Force - } -ModuleName $script:DSCResourceName -Verifiable - - It 'Should not throw an error when desired state is not equal to Started' { - { Set-TargetResource @testParameters } | Should Not Throw - } + $mockDynamicEndpointState = $mockEndpointStateStarted - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state is not equal to Started' { - Set-TargetResource @testParameters - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It - } + Context 'When desired state should be Stopped, but the current state is Started' { + It 'Should call the mock function Set-SqlHADREndpoint to set the state to Stopped' { + $testParameters.Add('State', $mockEndpointStateStopped) - It 'Should call the mock function Set-SqlHADREndpoint when desired state is not equal to Started' { - Set-TargetResource @testParameters - Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } + Set-TargetResource @testParameters - Context 'When the system is in the desired state' { - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Stopped' + Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 1 -Scope It + } + } } - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Stopped' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable + Context 'When the system is in the desired state' { + $mockDynamicEndpointState = $mockEndpointStateStarted - It 'Should not throw an error when desired state is equal to Stopped' { - { Set-TargetResource @testParameters } | Should Not Throw - } + Context 'When desired state should be Started, and the current state is Started' { + It 'Should not call the mock function Set-SqlHADREndpoint' { + $testParameters.Add('State', $mockEndpointStateStarted) - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state is equal to Stopped' { - Set-TargetResource @testParameters - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Set-TargetResource @testParameters - It 'Should not call the mock function Set-SqlHADREndpoint when desired state is equal to Stopped' { - Set-TargetResource @testParameters - Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 0 -Scope It + } + } - $testParameters = $defaultParameters - $testParameters += @{ - State = 'Started' - } + $mockDynamicEndpointState = $mockEndpointStateStopped - Mock -CommandName Get-SQLAlwaysOnEndpoint -MockWith { - # TypeName: Microsoft.SqlServer.Management.Smo.Endpoint - return New-Object Object | - Add-Member NoteProperty EndpointState 'Started' -PassThru -Force # TypeName: Microsoft.SqlServer.Management.Smo.EndpointState - } -ModuleName $script:DSCResourceName -Verifiable + Context 'When desired state should be Stopped, and the current state is Stopped' { + It 'Should not call the mock function Set-SqlHADREndpoint' { + $testParameters.Add('State', $mockEndpointStateStopped) - It 'Should not throw an error when desired state is equal to Started' { - { Set-TargetResource @testParameters } | Should Not Throw - } + Set-TargetResource @testParameters - It 'Should call the mock function Get-SQLAlwaysOnEndpoint when desired state is equal to Started' { - Set-TargetResource @testParameters - Assert-MockCalled Get-SQLAlwaysOnEndpoint -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 0 -Scope It + } + } + + Context 'When Get-TargetResource returns nothing' { + It 'Should throw the correct error message' { + Mock -CommandName Get-TargetResource -MockWith { + return $null + } -Verifiable + + { Set-TargetResource @testParameters } | Should Throw 'Got unexpected result from Get-TargetResource. No change is made.' + + Assert-MockCalled Connect-SQL -Exactly -Times 0 -Scope It + } + } } - It 'Should not call the mock function Set-SqlHADREndpoint when desired state is equal to Started' { - Set-TargetResource @testParameters - Assert-MockCalled Set-SqlHADREndpoint -Exactly -Times 0 -ModuleName $script:DSCResourceName -Scope It - } + Assert-VerifiableMocks } - - Assert-VerifiableMocks } } finally { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 index 338311c62..cc40cc939 100644 --- a/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerFirewall.Tests.ps1 @@ -92,6 +92,11 @@ try $mockmockSourceCredentialPassword = "dummyPassw0rd" | ConvertTo-SecureString -asPlainText -Force $mockSourceCredential = New-Object System.Management.Automation.PSCredential( $mockmockSourceCredentialUserName, $mockmockSourceCredentialPassword ) + $mockDynamicSQLEngineFirewallRulePresent = $true + $mockDynamicSQLBrowserFirewallRulePresent = $true + $mockDynamicSQLIntegrationServicesRulePresent = $true + $mockDynamicSQLAnalysisServicesFirewallRulePresent = $true + #region Function mocks $mockEmptyHashtable = { return @() @@ -246,7 +251,7 @@ try } $mockGetNetFirewallApplicationFilter = { - if ($AssociatedNetFirewallRule.DisplayName -eq "SQL Server Database Engine instance $mockCurrentInstanceName") + if ($mockDynamicSQLEngineFirewallRulePresent -and $AssociatedNetFirewallRule.DisplayName -eq "SQL Server Database Engine instance $mockCurrentInstanceName") { return @( ( @@ -255,7 +260,7 @@ try ) ) } - elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Integration Services Application') + elseif ($mockDynamicSQLIntegrationServicesRulePresent -and $AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Integration Services Application') { return @( ( @@ -264,14 +269,15 @@ try ) ) } - else + # Only throw if the rules should be present. + elseif ($mockDynamicSQLEngineFirewallRulePresent -and $mockDynamicSQLIntegrationServicesRulePresent) { 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") + if ($mockDynamicSQLAnalysisServicesFirewallRulePresent -and $AssociatedNetFirewallRule.DisplayName -eq "SQL Server Analysis Services instance $mockCurrentInstanceName") { return @( ( @@ -280,7 +286,7 @@ try ) ) } - elseif ($AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Browser') + elseif ($mockDynamicSQLBrowserFirewallRulePresent -and $AssociatedNetFirewallRule.DisplayName -eq 'SQL Server Browser') { return @( ( @@ -289,7 +295,7 @@ try ) ) } - else + elseif ($mockDynamicSQLBrowserFirewallRulePresent -and $mockDynamicSQLAnalysisServicesFirewallRulePresent) { throw "Mock Get-NetFirewallServiceFilter was called with a rule containing an unknown display name; $($AssociatedNetFirewallRule.DisplayName)" } @@ -761,6 +767,119 @@ try } } + 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 -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 + } + + # Change the mock to not return a rule for DB Engine + $mockDynamicSQLBrowserFirewallRulePresent = $true + $mockDynamicSQLIntegrationServicesRulePresent = $true + $mockDynamicSQLEngineFirewallRulePresent = $false + $mockDynamicSQLAnalysisServicesFirewallRulePresent = $true + + Context 'SQLBrowser rule is present, but missing SQLEngine rule' { + 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 $false for the read parameter DatabaseEngineFirewall' { + $result = Get-TargetResource @testParameters + $result.DatabaseEngineFirewall | Should Be $false + } + + It 'Should return $true for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $true + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + $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 + } + } + + # Change the mock to not return a rule for Analysis Services + $mockDynamicSQLBrowserFirewallRulePresent = $true + $mockDynamicSQLIntegrationServicesRulePresent = $true + $mockDynamicSQLEngineFirewallRulePresent = $true + $mockDynamicSQLAnalysisServicesFirewallRulePresent = $false + + Context 'SQLBrowser rule is present, but missing Analysis Services rule' { + 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 $false for the read parameter AnalysisServicesFirewall' { + $result = Get-TargetResource @testParameters + $result.AnalysisServicesFirewall | Should Be $false + } + + It 'Should return $true for the read parameter BrowserFirewall' { + $result = Get-TargetResource @testParameters + $result.BrowserFirewall | Should Be $true + } + + It 'Should return state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + $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 + } + } + } + + # Set mock to return all rules. + $mockDynamicSQLBrowserFirewallRulePresent = $true + $mockDynamicSQLIntegrationServicesRulePresent = $true + $mockDynamicSQLEngineFirewallRulePresent = $true + $mockDynamicSQLAnalysisServicesFirewallRulePresent = $true + Context "When SQL Server version is $mockCurrentSqlMajorVersion and the system is in the desired state for named instance" { BeforeEach { $testParameters = $mockDefaultParameters.Clone() diff --git a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 index 1ef784f43..2aba40497 100644 --- a/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerLogin.Tests.ps1 @@ -6,10 +6,10 @@ $script:DSCResourceName = 'MSFT_xSQLServerLogin' #region HEADER -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +# 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'))) ) + (-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\')) } @@ -19,723 +19,893 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion HEADER +function Invoke-TestSetup { + Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force + Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + # Begin Testing try { - #region Pester Test Initialization + Invoke-TestSetup - Import-Module -Name ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SQLPSStub.psm1 ) -Force - Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stubs ) -ChildPath SMO.cs ) + InModuleScope $script:DSCResourceName { + # 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 ) - # 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 ) - $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 ) - $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 ) - $mockSqlLoginOtherPassword = 'other' | ConvertTo-SecureString -AsPlainText -Force - $mockSqlLoginCredentialOtherPassword = New-Object System.Management.Automation.PSCredential( $mockSqlLoginUser, $mockSqlLoginOtherPassword ) + $instanceParameters = @{ + SQLInstanceName = 'MSSQLSERVER' + SQLServer = 'Server1' + } - $instanceParameters = @{ - SQLInstanceName = 'MSSQLSERVER' - SQLServer = 'Server1' - } + $getTargetResource_UnknownSqlLogin = $instanceParameters.Clone() + $getTargetResource_UnknownSqlLogin.Add( 'Name','UnknownSqlLogin' ) + + $getTargetResource_UnknownWindows = $instanceParameters.Clone() + $getTargetResource_UnknownWindows.Add( 'Name','Windows\UserOrGroup' ) - $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 + $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' ) + + <# + These are set when the mocked methods Enable() and Disabled() are called. + Can be used to verify that the method was actually called or not called. + #> + $script:mockWasLoginClassMethodEnableCalled = $false + $script:mockWasLoginClassMethodDisabledCalled = $false + + $mockConnectSql = { + $windowsUser = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Windows\User1' ) + $windowsUser.LoginType = 'WindowsUser' + $windowsUser = $windowsUser | Add-Member -Name 'Disable' -MemberType ScriptMethod { + $script:mockWasLoginClassMethodDisabledCalled = $true + } -PassThru -Force + + $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 + + $sqlLoginDisabled = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Windows\UserDisabled' ) + $sqlLoginDisabled.LoginType = 'WindowsUser' + $sqlLoginDisabled.IsDisabled = $true + $sqlLoginDisabled = $sqlLoginDisabled | Add-Member -Name 'Enable' -MemberType ScriptMethod { + $script:mockWasLoginClassMethodEnableCalled = $true + } -PassThru -Force + + $mock = New-Object PSObject -Property @{ + LoginMode = 'Mixed' + Logins = @{ + $windowsUser.Name = $windowsUser + $windowsGroup.Name = $windowsGroup + $sqlLogin.Name = $sqlLogin + $sqlLoginDisabled.Name = $sqlLoginDisabled + } } + + return $mock } - return $mock - } + #endregion Pester Test Initialization - #endregion Pester Test Initialization + Describe 'MSFT_xSQLServerLogin\Get-TargetResource' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable -Scope Describe - Describe "$($script:DSCResourceName)\Get-TargetResource" { - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Verifiable -Scope Describe + Context 'When the login is Absent' { - Context 'When the login is Absent' { + It 'Should be Absent when an unknown SQL Login is provided' { + ( Get-TargetResource @getTargetResource_UnknownSqlLogin ).Ensure | Should Be 'Absent' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should be Absent when an unknown SQL Login is provided' { - ( Get-TargetResource @getTargetResource_UnknownSqlLogin ).Ensure | Should Be 'Absent' + It 'Should be Absent when an unknown Windows User or Group is provided' { + ( Get-TargetResource @getTargetResource_UnknownWindows ).Ensure | Should Be 'Absent' - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } } - It 'Should be Absent when an unknown Windows User or Group is provided' { - ( Get-TargetResource @getTargetResource_UnknownWindows ).Ensure | Should Be 'Absent' + Context 'When the login is Present' { + It 'Should be Present when a known SQL Login is provided' { + $result = Get-TargetResource @getTargetResource_KnownSqlLogin - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } - } + $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 - Context 'When the login is Present' { - It 'Should be Present when a known SQL Login is provided' { - $result = Get-TargetResource @getTargetResource_KnownSqlLogin + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - $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 be Present when a known Windows User is provided' { + $result = Get-TargetResource @getTargetResource_KnownWindowsUser - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsUser' + $result.LoginMustChangePassword | Should BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should be Present when a known Windows User is provided' { - $result = Get-TargetResource @getTargetResource_KnownWindowsUser + It 'Should be Present when a known Windows Group is provided' { + $result = Get-TargetResource @getTargetResource_KnownWindowsGroup - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'WindowsUser' - $result.LoginMustChangePassword | Should BeNullOrEmpty - $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty - $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsGroup' + $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 - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should be Present when a known Windows User is provided' { - $result = Get-TargetResource @getTargetResource_KnownWindowsGroup + It 'Should be return the correct values when a login is disabled' { + $mockGetTargetResourceParameters = $instanceParameters.Clone() + $mockGetTargetResourceParameters.Add( 'Name','Windows\UserDisabled' ) + $result = Get-TargetResource @mockGetTargetResourceParameters - $result.Ensure | Should Be 'Present' - $result.LoginType | Should Be 'WindowsGroup' - $result.LoginMustChangePassword | Should BeNullOrEmpty - $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty - $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty + $result.Ensure | Should Be 'Present' + $result.LoginType | Should Be 'WindowsUser' + $result.LoginMustChangePassword | Should BeNullOrEmpty + $result.LoginPasswordExpirationEnabled | Should BeNullOrEmpty + $result.LoginPasswordPolicyEnforced | Should BeNullOrEmpty + $result.Disabled | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } } } - } - Describe "$($script:DSCResourceName)\Test-TargetResource" { - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + Describe 'MSFT_xSQLServerLogin\Test-TargetResource' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - 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' ) + 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' ) - ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsureAbsent ) | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsureAbsent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - 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 $true when the specified Windows group is Absent' { + $testTargetResource_WindowsGroupAbsent_EnsureAbsent = $testTargetResource_WindowsGroupAbsent.Clone() + $testTargetResource_WindowsGroupAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsureAbsent ) | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsureAbsent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return $true when the specified SQL Login is Absent' { - $testTargetResource_SqlLoginAbsent_EnsureAbsent = $testTargetResource_SqlLoginAbsent.Clone() - $testTargetResource_SqlLoginAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) + 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 + ( Test-TargetResource @testTargetResource_SqlLoginAbsent_EnsureAbsent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -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' ) + 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 + ( Test-TargetResource @testTargetResource_WindowsUserPresent_EnsureAbsent ) | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -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' ) + 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 + ( Test-TargetResource @testTargetResource_WindowsGroupPresent_EnsureAbsent ) | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return $false when the specified SQL Login is Present' { - $testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent = $testTargetResource_SqlLoginPresentWithDefaultValues.Clone() - $testTargetResource_SqlLoginPresentWithDefaultValues_EnsureAbsent.Add( 'Ensure','Absent' ) + 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 + ( 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' ) + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsurePresent ) | Should Be $false + It 'Should be return $false when a login should be disabled but are enabled' { + $mockTestTargetResourceParameters = $instanceParameters.Clone() + $mockTestTargetResourceParameters.Add( 'Ensure','Present' ) + $mockTestTargetResourceParameters.Add( 'Name','Windows\User1' ) + $mockTestTargetResourceParameters.Add( 'Disabled', $true ) - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should Be $false + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return $false when the specified Windows group is Absent' { - $testTargetResource_WindowsGroupAbsent_EnsurePresent = $testTargetResource_WindowsGroupAbsent.Clone() - $testTargetResource_WindowsGroupAbsent_EnsurePresent.Add( 'Ensure','Present' ) + It 'Should be return $false when a login should be enabled but are disabled' { + $mockTestTargetResourceParameters = $instanceParameters.Clone() + $mockTestTargetResourceParameters.Add( 'Ensure','Present' ) + $mockTestTargetResourceParameters.Add( 'Name','Windows\UserDisabled' ) + $mockTestTargetResourceParameters.Add( 'Disabled', $false ) - ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsurePresent ) | Should Be $false + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } } - It 'Should return $false when the specified SQL Login is Absent' { - $testTargetResource_SqlLoginAbsent_EnsurePresent = $testTargetResource_SqlLoginAbsent.Clone() - $testTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) + 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' ) - ( Test-TargetResource @testTargetResource_SqlLoginAbsent_EnsurePresent ) | Should Be $false + ( Test-TargetResource @testTargetResource_WindowsUserAbsent_EnsurePresent ) | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return $true when the specified Windows user is Present' { - $testTargetResource_WindowsUserPresent_EnsurePresent = $testTargetResource_WindowsUserPresent.Clone() - $testTargetResource_WindowsUserPresent_EnsurePresent.Add( 'Ensure','Present' ) + It 'Should return $false when the specified Windows group is Absent' { + $testTargetResource_WindowsGroupAbsent_EnsurePresent = $testTargetResource_WindowsGroupAbsent.Clone() + $testTargetResource_WindowsGroupAbsent_EnsurePresent.Add( 'Ensure','Present' ) - ( Test-TargetResource @testTargetResource_WindowsUserPresent_EnsurePresent ) | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsGroupAbsent_EnsurePresent ) | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - It 'Should return $true when the specified Windows group is Present' { - $testTargetResource_WindowsGroupPresent_EnsurePresent = $testTargetResource_WindowsGroupPresent.Clone() - $testTargetResource_WindowsGroupPresent_EnsurePresent.Add( 'Ensure','Present' ) + It 'Should return $false when the specified SQL Login is Absent' { + $testTargetResource_SqlLoginAbsent_EnsurePresent = $testTargetResource_SqlLoginAbsent.Clone() + $testTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) - ( Test-TargetResource @testTargetResource_WindowsGroupPresent_EnsurePresent ) | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginAbsent_EnsurePresent ) | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - 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' ) + It 'Should return $true when the specified Windows user is Present' { + $testTargetResource_WindowsUserPresent_EnsurePresent = $testTargetResource_WindowsUserPresent.Clone() + $testTargetResource_WindowsUserPresent_EnsurePresent.Add( 'Ensure','Present' ) - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent ) | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsUserPresent_EnsurePresent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - 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 ) + It 'Should return $true when the specified Windows group is Present' { + $testTargetResource_WindowsGroupPresent_EnsurePresent = $testTargetResource_WindowsGroupPresent.Clone() + $testTargetResource_WindowsGroupPresent_EnsurePresent.Add( 'Ensure','Present' ) - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent ) | Should Be $true + ( Test-TargetResource @testTargetResource_WindowsGroupPresent_EnsurePresent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -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 ) + 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' ) - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent ) | Should Be $false + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithDefaultValues_EnsurePresent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } - 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 ) + 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 ) - ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent ) | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledTrue_EnsurePresent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -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 ) + 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_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent ) | Should Be $false + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordExpirationEnabledFalse_EnsurePresent ) | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 1 -Exactly - } + Assert-MockCalled -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 ) + 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_SqlLoginPresentWithDefaultValuesGoodPw_EnsurePresent ) | Should Be $true + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedTrue_EnsurePresent ) | Should Be $true - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 2 -Exactly - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -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 ) + 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_SqlLoginPresentWithDefaultValuesBadPw_EnsurePresent ) | Should Be $false + ( Test-TargetResource @testTargetResource_SqlLoginPresentWithPasswordPolicyEnforcedFalse_EnsurePresent ) | Should Be $false - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Connect-SQL -Scope It -Times 2 -Exactly + Assert-MockCalled -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 -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 } -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 -CommandName Connect-SQL -Scope It -Times 2 -Exactly + } + + It 'Should be return $true when a login is enabled' { + $mockTestTargetResourceParameters = $instanceParameters.Clone() + $mockTestTargetResourceParameters.Add( 'Ensure','Present' ) + $mockTestTargetResourceParameters.Add( 'Name','Windows\User1' ) + $mockTestTargetResourceParameters.Add( 'Disabled', $false ) + + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } + + It 'Should be return $true when a login is disabled' { + $mockTestTargetResourceParameters = $instanceParameters.Clone() + $mockTestTargetResourceParameters.Add( 'Ensure','Present' ) + $mockTestTargetResourceParameters.Add( 'Name','Windows\UserDisabled' ) + $mockTestTargetResourceParameters.Add( 'Disabled', $true ) + + $result = Test-TargetResource @mockTestTargetResourceParameters + $result | Should Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + } } } - } - 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 - } + Describe 'MSFT_xSQLServerLogin\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' { + BeforeEach { + $script:mockWasLoginClassMethodEnableCalled = $false + $script:mockWasLoginClassMethodDisabledCalled = $false + } - 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' ) + It 'Should drop the specified Windows User when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - Set-TargetResource @setTargetResource_WindowsGroupPresent_EnsureAbsent + $setTargetResource_WindowsUserPresent_EnsureAbsent = $setTargetResource_WindowsUserPresent.Clone() + $setTargetResource_WindowsUserPresent_EnsureAbsent.Add( 'Ensure','Absent' ) - 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 @setTargetResource_WindowsUserPresent_EnsureAbsent - 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' ) + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - Set-TargetResource @setTargetResource_SqlLoginPresent_EnsureAbsent + It 'Should enable the specified Windows User when it is disabled' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -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 - } + $mockSetTargetResourceParameters = $instanceParameters.Clone() + $mockSetTargetResourceParameters.Add( 'Ensure','Present' ) + $mockSetTargetResourceParameters.Add( 'Name','Windows\UserDisabled' ) + $mockSetTargetResourceParameters.Add( 'Disabled', $false ) - 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' ) + Set-TargetResource @mockSetTargetResourceParameters + $script:mockWasLoginClassMethodEnableCalled | Should -Be $true + $script:mockWasLoginClassMethodDisabledCalled | Should -Be $false - Set-TargetResource @setTargetResource_WindwsUserAbsent_EnsureAbsent + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - 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 disable the specified Windows User when it is enabled' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - 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' ) + $mockSetTargetResourceParameters = $instanceParameters.Clone() + $mockSetTargetResourceParameters.Add( 'Ensure','Present' ) + $mockSetTargetResourceParameters.Add( 'Name','Windows\User1' ) + $mockSetTargetResourceParameters.Add( 'Disabled', $true ) - Set-TargetResource @setTargetResource_WindwsGroupAbsent_EnsureAbsent + Set-TargetResource @mockSetTargetResourceParameters + $script:mockWasLoginClassMethodEnableCalled | Should -Be $false + $script:mockWasLoginClassMethodDisabledCalled | Should -Be $true - 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 - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - 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' ) + It 'Should drop the specified Windows Group when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsureAbsent + $setTargetResource_WindowsGroupPresent_EnsureAbsent = $setTargetResource_WindowsGroupPresent.Clone() + $setTargetResource_WindowsGroupPresent_EnsureAbsent.Add( 'Ensure','Absent' ) - 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 @setTargetResource_WindowsGroupPresent_EnsureAbsent - 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' ) + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - Set-TargetResource @setTargetResource_WindowsUserAbsent_EnsurePresent + It 'Should drop the specified SQL Login when it is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -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 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 - } + $setTargetResource_SqlLoginPresent_EnsureAbsent = $setTargetResource_SqlLoginPresent.Clone() + $setTargetResource_SqlLoginPresent_EnsureAbsent.Add( 'Ensure','Absent' ) - 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_SqlLoginPresent_EnsureAbsent - Set-TargetResource @setTargetResource_WindowsGroupAbsent_EnsurePresent + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - 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 do nothing when the specified Windows User is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - It 'Should add the specified SQL Login when it is Absent' { - Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -ModuleName $script:DSCResourceName -Scope It -Verifiable + $setTargetResource_WindowsUserAbsent_EnsureAbsent = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindowsUserAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - $setTargetResource_SqlLoginAbsent_EnsurePresent = $setTargetResource_SqlLoginAbsent.Clone() - $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'Ensure','Present' ) - $setTargetResource_SqlLoginAbsent_EnsurePresent.Add( 'LoginCredential',$mockSqlLoginCredential ) + Set-TargetResource @setTargetResource_WindowsUserAbsent_EnsureAbsent - Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - 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 do nothing when the specified Windows Group is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - 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 - } + $setTargetResource_WindowsGroupAbsent_EnsureAbsent = $setTargetResource_WindowsGroupAbsent.Clone() + $setTargetResource_WindowsGroupAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - 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_WindowsGroupAbsent_EnsureAbsent - { Set-TargetResource @setTargetResource_CertificateAbsent_EnsurePresent } | Should Throw 'LoginTypeNotImplemented' + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - 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 when the specified SQL Login is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - 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' ) + $setTargetResource_SqlLoginAbsent_EnsureAbsent = $setTargetResource_SqlLoginAbsent.Clone() + $setTargetResource_SqlLoginAbsent_EnsureAbsent.Add( 'Ensure','Absent' ) - { Set-TargetResource @setTargetResource_SqlLoginAbsent_EnsurePresent_NoCred } | Should Throw 'LoginCredentialNotFound' + Set-TargetResource @setTargetResource_SqlLoginAbsent_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 0 -Exactly - Assert-MockCalled -ModuleName $script:DSCResourceName -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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' ) + Context 'When the desired state is Present' { + BeforeEach { + $script:mockWasLoginClassMethodEnableCalled = $false + $script:mockWasLoginClassMethodDisabledCalled = $false + } - Set-TargetResource @setTargetResource_WindowsUserPresent_EnsurePresent + It 'Should add the specified Windows User when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -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 - } + $setTargetResource_WindowsUserAbsent_EnsurePresent = $setTargetResource_WindowsUserAbsent.Clone() + $setTargetResource_WindowsUserAbsent_EnsurePresent.Add( 'Ensure','Present' ) - 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_WindowsUserAbsent_EnsurePresent - Set-TargetResource @setTargetResource_WindowsGroupPresent_EnsurePresent + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } - 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 add the specified Windows User as disabled when it is Absent' { + Mock -CommandName Connect-SQL -MockWith { + return New-Object -TypeName PSObject -Property @{ + Logins = @{} + } + }-Verifiable + + Mock -CommandName New-Object -MockWith { + $windowsUser = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'Windows\User1' ) + $windowsUser = $windowsUser | Add-Member -Name 'Disable' -MemberType ScriptMethod { + $script:mockWasLoginClassMethodDisabledCalled = $true + } -PassThru -Force + + return $windowsUser + } -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Login' -and $ArgumentList[1] -eq 'Windows\UserAbsent' + }-Verifiable + + $mockSetTargetResourceParameters = $instanceParameters.Clone() + $mockSetTargetResourceParameters.Add( 'Ensure','Present' ) + $mockSetTargetResourceParameters.Add( 'Name','Windows\UserAbsent' ) + $mockSetTargetResourceParameters.Add( 'Disabled', $true ) + + Set-TargetResource @mockSetTargetResourceParameters + $script:mockWasLoginClassMethodDisabledCalled | Should -Be $true + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 ) + It 'Should add the specified Windows Group when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable - Set-TargetResource @setTargetResource_SqlLoginPresent_EnsurePresent + $setTargetResource_WindowsGroupAbsent_EnsurePresent = $setTargetResource_WindowsGroupAbsent.Clone() + $setTargetResource_WindowsGroupAbsent_EnsurePresent.Add( 'Ensure','Present' ) - 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 - } + Set-TargetResource @setTargetResource_WindowsGroupAbsent_EnsurePresent - 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 - } + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -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 add the specified SQL Login when it is Absent' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -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 -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -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 -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -Verifiable + + $setTargetResource_CertificateAbsent_EnsurePresent = $setTargetResource_CertificateAbsent.Clone() + $setTargetResource_CertificateAbsent_EnsurePresent.Add( 'Ensure','Present' ) + + { Set-TargetResource @setTargetResource_CertificateAbsent_EnsurePresent } | Should Throw 'LoginTypeNotImplemented' + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -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 -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -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 + It 'Should do nothing if the specified Windows User is Present' { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $setTargetResource_WindowsUserPresent_EnsurePresent = $setTargetResource_WindowsUserPresent.Clone() + $setTargetResource_WindowsUserPresent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsUserPresent_EnsurePresent + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -Verifiable + + $setTargetResource_WindowsGroupPresent_EnsurePresent = $setTargetResource_WindowsGroupPresent.Clone() + $setTargetResource_WindowsGroupPresent_EnsurePresent.Add( 'Ensure','Present' ) + + Set-TargetResource @setTargetResource_WindowsGroupPresent_EnsurePresent + + Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -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 -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -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 -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -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 -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -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 -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 -CommandName Connect-SQL -Scope It -Times 1 -Exactly + Assert-MockCalled -CommandName Update-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName New-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Remove-SQLServerLogin -Scope It -Times 0 -Exactly + Assert-MockCalled -CommandName Set-SQLServerLoginPassword -Scope It -Times 0 -Exactly + } } } - } - InModuleScope -ModuleName $script:DSCResourceName { - Describe "$($script:DSCResourceName)\Update-SQLServerLogin" { + Describe 'MSFT_xSQLServerLogin\Update-SQLServerLogin' { Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName Context 'When the Login is altered' { @@ -756,9 +926,9 @@ try } } - Describe "$($script:DSCResourceName)\New-SQLServerLogin" { + Describe 'MSFT_xSQLServerLogin\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' ) @@ -772,14 +942,14 @@ try $login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) $login.LoginType = 'SqlLogin' $login.MockLoginType = 'SqlLogin' - - $createLoginParams = @{ + + $createLoginParameters = @{ Login = $login SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force LoginCreateOptions = 'None' } - { New-SQLServerLogin @createLoginParams } | Should Not Throw + { New-SQLServerLogin @createLoginParameters } | Should Not Throw } It 'Should throw the correct error when login creation fails' { @@ -793,47 +963,47 @@ try 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 = @{ + + $createLoginParameters = @{ Login = $login SecureString = ConvertTo-SecureString -String 'pw' -AsPlainText -Force LoginCreateOptions = 'None' } - { New-SQLServerLogin @createLoginParams } | Should Throw 'PasswordValidationFailed' + { New-SQLServerLogin @createLoginParameters } | Should Throw 'PasswordValidationFailed' } 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 = @{ + + $createLoginParameters = @{ Login = $login SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force LoginCreateOptions = 'None' } - { New-SQLServerLogin @createLoginParams } | Should Throw 'LoginCreationFailedFailedOperation' + { New-SQLServerLogin @createLoginParameters } | Should Throw 'LoginCreationFailedFailedOperation' } 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 = @{ + + $createLoginParameters = @{ Login = $login SecureString = ConvertTo-SecureString -String 'P@ssw0rd-12P@ssw0rd-12' -AsPlainText -Force LoginCreateOptions = 'None' } - { New-SQLServerLogin @createLoginParams } | Should Throw 'LoginCreationFailedSqlNotSpecified' + { New-SQLServerLogin @createLoginParameters } | Should Throw 'LoginCreationFailedSqlNotSpecified' } } } - Describe "$($script:DSCResourceName)\Remove-SQLServerLogin" { + Describe 'MSFT_xSQLServerLogin\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' ) @@ -852,44 +1022,44 @@ try } } - Describe "$($script:DSCResourceName)\Set-SQLServerLoginPassword" { + Describe 'MSFT_xSQLServerLogin\Set-SQLServerLoginPassword' { Mock -CommandName New-TerminatingError -MockWith { $ErrorType } -ModuleName $script:DSCResourceName Context 'When the password is set on an existing login' { It 'Should silently set the password' { - $setPwParams = @{ + $setPasswordParameters = @{ 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-SQLServerLoginPassword @setPasswordParameters } | Should Not Throw } It 'Should throw the correct error when password validation fails' { - $setPwParams = @{ + $setPasswordParameters = @{ Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) SecureString = ConvertTo-SecureString -String 'pw' -AsPlainText -Force } - - { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordValidationFailed' + + { Set-SQLServerLoginPassword @setPasswordParameters } | Should Throw 'PasswordValidationFailed' } It 'Should throw the correct error when changing the password fails' { - $setPwParams = @{ + $setPasswordParameters = @{ Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) SecureString = ConvertTo-SecureString -String 'reused' -AsPlainText -Force } - - { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordChangeFailed' + + { Set-SQLServerLoginPassword @setPasswordParameters } | Should Throw 'PasswordChangeFailed' } It 'Should throw the correct error when changing the password fails' { - $setPwParams = @{ + $setPasswordParameters = @{ Login = New-Object Microsoft.SqlServer.Management.Smo.Login( 'Server', 'dba' ) SecureString = ConvertTo-SecureString -String 'other' -AsPlainText -Force } - - { Set-SQLServerLoginPassword @setPwParams } | Should Throw 'PasswordChangeFailed' + + { Set-SQLServerLoginPassword @setPasswordParameters } | Should Throw 'PasswordChangeFailed' } } } @@ -897,9 +1067,5 @@ try } finally { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xSQLServerMaxDop.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerMaxDop.Tests.ps1 index 10e01e63b..5be7f2b84 100644 --- a/Tests/Unit/MSFT_xSQLServerMaxDop.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerMaxDop.Tests.ps1 @@ -324,6 +324,19 @@ try } } + # This is regression test for issue #576 + Context 'When the system is in the desired state and SQLServer is not set' { + $testParameters = $mockDefaultParameters + $testParameters.Remove('SQLServer') + $testParameters += @{ + Ensure = 'Absent' + } + + It 'Should not throw an error' { + { Test-TargetResource @testParameters } | Should Not Throw + } + } + Assert-VerifiableMocks } diff --git a/Tests/Unit/MSFT_xSQLServerMemory.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerMemory.Tests.ps1 index 3c3752b89..8b996def0 100644 --- a/Tests/Unit/MSFT_xSQLServerMemory.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerMemory.Tests.ps1 @@ -395,6 +395,19 @@ try } } + # This is regression test for issue #576 + Context 'When the system is in the desired state and SQLServer is not set' { + $testParameters = $mockDefaultParameters + $testParameters.Remove('SQLServer') + $testParameters += @{ + Ensure = 'Absent' + } + + It 'Should not throw an error' { + { Test-TargetResource @testParameters } | Should Not Throw + } + } + Assert-VerifiableMocks } @@ -647,7 +660,7 @@ try } It 'Should throw the correct error' { - { Set-TargetResource @testParameters } | Should Throw ("Failed to alter the server configuration memory for $mockSQLServerName" + "\" +` + { Set-TargetResource @testParameters } | Should Throw ("Failed to alter the server configuration memory for $($env:COMPUTERNAME)" + "\" +` "$mockSQLServerInstanceName. InnerException: Exception calling ""Alter"" with ""0"" argument(s): " + ` """Mock Alter Method was called with invalid operation.""") } diff --git a/Tests/Unit/MSFT_xSQLServerNetwork.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerNetwork.Tests.ps1 new file mode 100644 index 000000000..5c1794b3f --- /dev/null +++ b/Tests/Unit/MSFT_xSQLServerNetwork.Tests.ps1 @@ -0,0 +1,486 @@ +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerNetwork' + +#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 { + $mockInstanceName = 'TEST' + $mockTcpProtocolName = 'Tcp' + $mockNamedPipesProtocolName = 'NP' + + $script:WasMethodAlterCalled = $false + + $mockFunction_NewObject_ManagedComputer = { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'ServerInstances' { + return @{ + $mockInstanceName = New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'ServerProtocols' { + return @{ + $mockDynamicValue_TcpProtocolName = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'IsEnabled' -Value $mockDynamicValue_IsEnabled -PassThru | + Add-Member -MemberType ScriptProperty -Name 'IPAddresses' { + return @{ + 'IPAll' = New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'IPAddressProperties' { + return @{ + 'TcpDynamicPorts' = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Value' -Value $mockDynamicValue_TcpDynamicPorts -PassThru -Force + 'TcpPort' = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Value' -Value $mockDynamicValue_TcpPort -PassThru -Force + } + } -PassThru -Force + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' { + <# + It is not possible to verify that the correct value was set here for TcpDynamicPorts and + TcpPort with the current implementation. + If `$this.IPAddresses['IPAll'].IPAddressProperties['TcpDynamicPorts'].Value` would be + called it would just return the same value over an over again, not the value that was + set in the function Set-TargetResource. + To be able to do this check for TcpDynamicPorts and TcpPort, then the class must be mocked + in SMO.cs. + #> + if ($this.IsEnabled -ne $mockExpectedValue_IsEnabled) + { + throw ('Mock method Alter() was called with an unexpected value for IsEnabled. Expected ''{0}'', but was ''{1}''' -f $mockExpectedValue_IsEnabled, $this.IsEnabled) + } + + # This can be used to verify so that alter method was actually called. + $script:WasMethodAlterCalled = $true + } -PassThru -Force + } + } -PassThru -Force + } + } -PassThru -Force + } + + $mockFunction_NewObject_ManagedComputer_ParameterFilter = { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer' + } + + $mockFunction_RegisterSqlWmiManagement = { + return [System.AppDomain]::CreateDomain('DummyTestApplicationDomain') + } + + $mockDefaultParameters = @{ + InstanceName = $mockInstanceName + ProtocolName = $mockTcpProtocolName + } + + Describe "MSFT_xSQLServerNetwork\Get-TargetResource" -Tag 'Get'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Register-SqlWmiManagement ` + -MockWith $mockFunction_RegisterSqlWmiManagement ` + -Verifiable + + Mock -CommandName New-Object ` + -MockWith $mockFunction_NewObject_ManagedComputer ` + -ParameterFilter $mockFunction_NewObject_ManagedComputer_ParameterFilter -Verifiable + } + + Context 'When Get-TargetResource is called' { + BeforeEach { + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPorts = '0' + $mockDynamicValue_TcpPort = '4509' + } + + It 'Should return the correct values' { + $result = Get-TargetResource @testParameters + $result.IsEnabled | Should Be $mockDynamicValue_IsEnabled + $result.TcpDynamicPorts | Should Be $mockDynamicValue_TcpDynamicPorts + $result.TcpPort | Should Be $mockDynamicValue_TcpPort + + Assert-MockCalled -CommandName Register-SqlWmiManagement -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockFunction_NewObject_ManagedComputer_ParameterFilter + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.InstanceName | Should Be $testParameters.InstanceName + $result.ProtocolName | Should Be $testParameters.ProtocolName + } + } + + Assert-VerifiableMocks + } + + Describe "MSFT_xSQLServerNetwork\Test-TargetResource" -Tag 'Test'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Register-SqlWmiManagement ` + -MockWith $mockFunction_RegisterSqlWmiManagement ` + -Verifiable + + Mock -CommandName New-Object ` + -MockWith $mockFunction_NewObject_ManagedComputer ` + -ParameterFilter $mockFunction_NewObject_ManagedComputer_ParameterFilter -Verifiable + } + + Context 'When the system is not in the desired state' { + BeforeEach { + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPorts = '' + $mockDynamicValue_TcpPort = '4509' + } + + <# + This test does not work until support for more than one protocol + is added. See issue #14. + #> + <# + Context 'When Protocol is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $true + TcpDynamicPorts = '' + TcpPort = '4509' + } + + $testParameters.ProtocolName = $mockNamedPipesProtocolName + } + + It 'Should return $false' { + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + } + #> + + Context 'When IsEnabled is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $false + TcpDynamicPorts = '' + TcpPort = '4509' + } + } + + It 'Should return $false' { + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + } + + Context 'When current state is using static tcp port' { + Context 'When TcpDynamicPorts is not in desired state' { + BeforeEach { + $testParameters += @{ + TcpDynamicPorts = '0' + IsEnabled = $false + } + } + + It 'Should return $false' { + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + } + + Context 'When TcpPort is not in desired state' { + BeforeEach { + $testParameters += @{ + TcpPort = '1433' + IsEnabled = $true + } + } + + It 'Should return $false' { + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + } + } + + Context 'When current state is using dynamic tcp port' { + BeforeEach { + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPorts = '0' + $mockDynamicValue_TcpPort = '' + } + + Context 'When TcpPort is not in desired state' { + BeforeEach { + $testParameters += @{ + TcpPort = '1433' + IsEnabled = $true + } + } + + It 'Should return $false' { + $result = Test-TargetResource @testParameters + $result | Should Be $false + } + } + } + + Context 'When both TcpDynamicPorts and TcpPort is being set' { + BeforeEach { + $testParameters += @{ + TcpDynamicPorts = '0' + TcpPort = '1433' + IsEnabled = $false + } + } + + It 'Should throw the correct error message' { + { Test-TargetResource @testParameters } | Should -Throw 'Unable to set both tcp dynamic port and tcp static port. Only one can be set.' + } + } + } + + Context 'When the system is in the desired state' { + BeforeEach { + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPorts = '0' + $mockDynamicValue_TcpPort = '1433' + } + + Context 'When TcpPort is in desired state' { + BeforeEach { + $testParameters += @{ + TcpPort = '1433' + IsEnabled = $true + } + } + + It 'Should return $true' { + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + } + + Context 'When TcpDynamicPorts is in desired state' { + BeforeEach { + $testParameters += @{ + TcpDynamicPorts = '0' + IsEnabled = $true + } + } + + It 'Should return $true' { + $result = Test-TargetResource @testParameters + $result | Should Be $true + } + } + } + + Assert-VerifiableMocks + } + + Describe "MSFT_xSQLServerNetwork\Set-TargetResource" -Tag 'Set'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Restart-SqlService -Verifiable + Mock -CommandName Register-SqlWmiManagement ` + -MockWith $mockFunction_RegisterSqlWmiManagement ` + -Verifiable + + Mock -CommandName New-Object ` + -MockWith $mockFunction_NewObject_ManagedComputer ` + -ParameterFilter $mockFunction_NewObject_ManagedComputer_ParameterFilter -Verifiable + + # This is used to evaluate if mocked Alter() method was called. + $script:WasMethodAlterCalled = $false + } + + Context 'When the system is not in the desired state' { + BeforeEach { + # This is the values the mock will return + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPorts = '' + $mockDynamicValue_TcpPort = '4509' + + <# + This is the values we expect to be set when Alter() method is called. + These values will set here to same as the values the mock will return, + but before each test the these will be set to the correct value that is + expected to be returned for that particular test. + #> + $mockExpectedValue_IsEnabled = $mockDynamicValue_IsEnabled + } + + Context 'When IsEnabled is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $false + TcpDynamicPorts = '' + TcpPort = '4509' + RestartService = $true + } + + $mockExpectedValue_IsEnabled = $false + } + + It 'Should call Set-TargetResource without throwing and should call Alter()' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:WasMethodAlterCalled | Should -Be $true + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 1 -Scope It + } + } + + Context 'When current state is using static tcp port' { + Context 'When TcpDynamicPorts is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $true + TcpDynamicPorts = '0' + } + } + + It 'Should call Set-TargetResource without throwing and should call Alter()' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:WasMethodAlterCalled | Should -Be $true + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 0 -Scope It + } + } + + Context 'When TcpPort is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $true + TcpDynamicPorts = '' + TcpPort = '4508' + } + } + + It 'Should call Set-TargetResource without throwing and should call Alter()' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:WasMethodAlterCalled | Should -Be $true + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 0 -Scope It + } + } + } + + Context 'When current state is using dynamic tcp port ' { + BeforeEach { + # This is the values the mock will return + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPorts = '0' + $mockDynamicValue_TcpPort = '' + + <# + This is the values we expect to be set when Alter() method is called. + These values will set here to same as the values the mock will return, + but before each test the these will be set to the correct value that is + expected to be returned for that particular test. + #> + $mockExpectedValue_IsEnabled = $mockDynamicValue_IsEnabled + } + + Context 'When TcpPort is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $true + TcpDynamicPorts = '' + TcpPort = '4508' + } + } + + It 'Should call Set-TargetResource without throwing and should call Alter()' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:WasMethodAlterCalled | Should -Be $true + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 0 -Scope It + } + } + } + + Context 'When both TcpDynamicPorts and TcpPort is being set' { + BeforeEach { + $testParameters += @{ + TcpDynamicPorts = '0' + TcpPort = '1433' + IsEnabled = $false + } + } + + It 'Should throw the correct error message' { + { Set-TargetResource @testParameters } | Should -Throw 'Unable to set both tcp dynamic port and tcp static port. Only one can be set.' + } + } + } + + Context 'When the system is in the desired state' { + BeforeEach { + # This is the values the mock will return + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPorts = '' + $mockDynamicValue_TcpPort = '4509' + + <# + We do not expect Alter() method to be called. So we set this to $null so + if it is, then it will throw an error. + #> + $mockExpectedValue_IsEnabled = $null + + $testParameters += @{ + IsEnabled = $true + TcpDynamicPorts = '' + TcpPort = '4509' + } + } + + It 'Should call Set-TargetResource without throwing and should not call Alter()' { + { Set-TargetResource @testParameters } | Should -Not -Throw + $script:WasMethodAlterCalled | Should -Be $false + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 0 -Scope It + } + } + + Assert-VerifiableMocks + } + } +} +finally +{ + Invoke-TestCleanup +} + diff --git a/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 index 273563bad..bc0d444c0 100644 --- a/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerPermission.Tests.ps1 @@ -3,10 +3,10 @@ $script:DSCResourceName = 'MSFT_xSQLServerPermission' #region HEADER -# Unit Test Template Version: 1.1.0 -[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +# 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'))) ) + (-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\')) } @@ -16,336 +16,313 @@ Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\ $TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:DSCModuleName ` -DSCResourceName $script:DSCResourceName ` - -TestType Unit + -TestType Unit #endregion HEADER +function Invoke-TestSetup { + # Loading mocked classes + Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') +} + +function Invoke-TestCleanup { + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + # Begin Testing try { - #region Pester Test Initialization + Invoke-TestSetup + + InModuleScope $script:DSCResourceName { + $mockNodeName = 'localhost' + $mockInstanceName = 'DEFAULT' + $mockPrincipal = 'COMPANY\SqlServiceAcct' + $mockOtherPrincipal = 'COMPANY\OtherAccount' + $mockPermission = @('ConnectSql','AlterAnyAvailabilityGroup','ViewServerState') + + #endregion Pester Test Initialization + + $defaultParameters = @{ + InstanceName = $mockInstanceName + NodeName = $mockNodeName + Principal = $mockPrincipal + Permission = $mockPermission + } - # Loading mocked classes - Add-Type -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\Unit\Stubs\SMO.cs') + Describe 'MSFT_xSQLServerPermission\Get-TargetResource' { + BeforeEach { + $testParameters = $defaultParameters.Clone() - $nodeName = 'localhost' - $instanceName = 'DEFAULT' - $principal = 'COMPANY\SqlServiceAcct' - $permission = @( 'AlterAnyAvailabilityGroup','ViewServerState' ) + Mock -CommandName Connect-SQL -MockWith { + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$mockNodeName\$mockInstanceName" + $mockObjectSmoServer.DisplayName = $mockInstanceName + $mockObjectSmoServer.InstanceName = $mockInstanceName + $mockObjectSmoServer.IsHadrEnabled = $False + $mockObjectSmoServer.MockGranteeName = $mockPrincipal - #endregion Pester Test Initialization + return $mockObjectSmoServer + } -Verifiable + } - $defaultParameters = @{ - InstanceName = $instanceName - NodeName = $nodeName - Principal = $principal - Permission = $permission - } + Context 'When the system is not in the desired state' { + Context 'When no permission is set for the principal' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - 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 + It 'Should return the desired state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } - $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 the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.NodeName | Should Be $mockNodeName + $result.InstanceName | Should Be $mockInstanceName + $result.Principal | Should Be $mockPrincipal + } - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters + It 'Should not return any permissions' { + $result = Get-TargetResource @testParameters + $result.Permission | Should Be '' + } - $result = Get-TargetResource @testParameters + It 'Should call the mock function Connect-SQL' { + Get-TargetResource @testParameters | Out-Null + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } - It 'Should return the desired state as absent' { - $result.Ensure | Should Be 'Absent' - } + Context 'When one permission is missing for the principal' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true - 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 - } + BeforeEach { + $testParameters.Permission = @( 'AlterAnyAvailabilityGroup','ViewServerState','AlterAnyEndpoint') + } - It 'Should not return any permissions' { - $result.Permission | Should Be $null - } + It 'Should return the desired state as absent' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + } - 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 + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.NodeName | Should Be $mockNodeName + $result.InstanceName | Should Be $mockInstanceName + $result.Principal | Should Be $mockPrincipal + } - $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 not return any permissions' { + $result = Get-TargetResource @testParameters + $result.Permission | Should Be @('AlterAnyAvailabilityGroup','ConnectSql', 'ViewServerState') + } - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters + It 'Should call the mock function Connect-SQL' { + Get-TargetResource @testParameters | Out-Null + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } - $result = Get-TargetResource @testParameters + Context 'When the Get-TargetResource throws an error' { + It 'Should return the correct error message' { + Mock -CommandName Connect-Sql -MockWith { + throw 'Mocked error.' + } - It 'Should return the desired state as present' { - $result.Ensure | Should Be 'Present' + { Get-TargetResource @testParameters } | Should Throw 'Unexpected result when trying to get permissions for ''COMPANY\SqlServiceAcct''. InnerException: Mocked error.' + } + } } - 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 - } + Context 'When the system is in the desired state' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + + It 'Should return the desired state as present' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + } - It 'Should return the permissions passed as parameter' { - foreach ($currentPermission in $permission) { - if( $result.Permission -ccontains $currentPermission ) { - $permissionState = $true - } else { - $permissionState = $false - break + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.NodeName | Should Be $mockNodeName + $result.InstanceName | Should Be $mockInstanceName + $result.Principal | Should Be $mockPrincipal + } + + It 'Should return the permissions passed as parameter' { + $result = Get-TargetResource @testParameters + foreach ($currentPermission in $mockPermission) { + 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 + $permissionState | Should Be $true + } + + It 'Should call the mock function Connect-SQL' { + Get-TargetResource @testParameters | Out-Null + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - } - Assert-VerifiableMocks - } + 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 + Describe 'MSFT_xSQLServerPermission\Test-TargetResource' { + BeforeEach { + $testParameters = $defaultParameters.Clone() + Mock -CommandName Connect-SQL -MockWith { $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.Name = "$mockNodeName\$mockInstanceName" + $mockObjectSmoServer.DisplayName = $mockInstanceName + $mockObjectSmoServer.InstanceName = $mockInstanceName $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal + $mockObjectSmoServer.MockGranteeName = $mockPrincipal return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + } -Verifiable + } + + 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' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - $result = Test-TargetResource @testParameters - $result | Should Be $false + $testParameters.Add('Ensure', 'Present') - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } - It 'Should return that desired state is absent when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { + It 'Should return that desired state is absent when wanted desired state is to be Absent' { [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 + $testParameters.Add('Ensure', 'Absent') - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } - $result = Test-TargetResource @testParameters - $result | Should Be $false + $result = Test-TargetResource @testParameters + $result | Should Be $false - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Connect-SQL -Exactly -Times 1 -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 { + 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' { [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 + $testParameters.Add('Ensure', 'Present') - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } - It 'Should return that desired state is present when wanted desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { + It 'Should return that desired state is present when wanted desired state is to be Absent' { [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 + $testParameters.Add('Ensure', 'Absent') - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' - } + $result = Test-TargetResource @testParameters + $result | Should Be $true - $result = Test-TargetResource @testParameters - $result | Should Be $true - - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } } - } - Assert-VerifiableMocks - } + 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 + Describe 'MSFT_xSQLServerPermission\Set-TargetResource' { + BeforeEach { + $testParameters = $defaultParameters.Clone() + Mock -CommandName Connect-SQL -MockWith { $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server - $mockObjectSmoServer.Name = "$nodeName\$instanceName" - $mockObjectSmoServer.DisplayName = $instanceName - $mockObjectSmoServer.InstanceName = $instanceName + $mockObjectSmoServer.Name = "$mockNodeName\$mockInstanceName" + $mockObjectSmoServer.DisplayName = $mockInstanceName + $mockObjectSmoServer.InstanceName = $mockInstanceName $mockObjectSmoServer.IsHadrEnabled = $False - $mockObjectSmoServer.MockGranteeName = $principal + $mockObjectSmoServer.MockGranteeName = $mockPrincipal return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } - - { Set-TargetResource @testParameters } | Should Not Throw - - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + } -Verifiable } - It 'Should not throw error when desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + Context 'When the system is not in the desired state' { + It 'Should not throw error when desired state is to be Present' { + [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 + $testParameters.Add('Ensure', 'Present') - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Absent' + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -Scope It } - { Set-TargetResource @testParameters } | Should Not Throw + It 'Should not throw error when desired state is to be Absent' { + [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + + $testParameters.Add('Ensure', 'Absent') - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 2 -ModuleName $script:DSCResourceName -Scope It + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 2 -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 { + Context 'When the system is in the desired state' { + It 'Should not throw error when desired state is to be Present' { [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()' + $testParameters.Add('Ensure', 'Present') - return $mockObjectSmoServer - } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Ensure = 'Present' - } + { 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 Connect-SQL -Exactly -Times 1 -Scope It + } - It 'Should not throw error when desired state is to be Absent' { - Mock -CommandName Get-SQLPSInstance -MockWith { + It 'Should not throw error when desired state is to be Absent' { [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()' + $testParameters.Add('Ensure', 'Absent') - 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 Connect-SQL -Exactly -Times 1 -Scope It + } - Assert-MockCalled Get-SQLPSInstance -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It + Context 'When the Set-TargetResource throws an error' { + It 'Should return the correct error message' { + Mock -CommandName Connect-SQL -MockWith { + $mockObjectSmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server + $mockObjectSmoServer.Name = "$mockNodeName\$mockInstanceName" + $mockObjectSmoServer.DisplayName = $mockInstanceName + $mockObjectSmoServer.InstanceName = $mockInstanceName + $mockObjectSmoServer.IsHadrEnabled = $False + # This make the SMO Server object mock to throw when Grant() method is called. + $mockObjectSmoServer.MockGranteeName = $mockOtherPrincipal + + return $mockObjectSmoServer + } -Verifiable + + { Set-TargetResource @testParameters } | Should Throw 'Changing permission for principal ''COMPANY\SqlServiceAcct'' failed. InnerException: Exception calling "Grant" with "2" argument(s): "Expected to get granteeName == ''COMPANY\OtherAccount''. But got ''COMPANY\SqlServiceAcct''' + } + } } + + Assert-VerifiableMocks } -#> - Assert-VerifiableMocks } } finally { - #region FOOTER - - Restore-TestEnvironment -TestEnvironment $TestEnvironment - - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 index 451988992..6b965b730 100644 --- a/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerRole.Tests.ps1 @@ -1,263 +1,968 @@ -$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 - -$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 - ServerRole = 'dbcreator' - } - - #endregion Pester Test Initialization - - 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 - - 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 - - $result = Get-TargetResource @testParameters - - 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 - } - - 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 - - $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 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 - } - } - - Assert-VerifiableMocks - } - - 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 = Test-TargetResource @testParameters - $result | Should Be $false - - 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 false when non-desired loginName exist' { - - Mock -CommandName Confirm-SqlServerRoleMember -MockWith { return $true } -ModuleName $script:DSCResourceName -Verifiable - - $testParameters = $defaultParameters - $testParameters += @{ - Name = 'NonDesiredUser' - Ensure = 'Absent' - } - - $result = Test-TargetResource @testParameters - $result | Should Be $false - - Assert-MockCalled Connect-SQL -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - Assert-MockCalled Confirm-SqlServerRoleMember -Exactly -Times 1 -ModuleName $script:DSCResourceName -Scope It - } - } - - 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' - } - - $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 - } - - 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 - - $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-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' { - $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 - } - - 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' - } - } - - 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' { - - 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 - - #endregion -} +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xSQLServerRole' + +#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 { + $mockSqlServerName = 'localhost' + $mockSqlServerInstanceName = 'MSSQLSERVER' + $mockSqlServerRole = 'AdminSqlforBI' + $mockSqlServerLoginOne = 'CONTOSO\John' + $mockSqlServerLoginTwo = 'CONTOSO\Kelly' + $mockSqlServerLoginTree = 'CONTOSO\Lucy' + $mockSqlServerLoginFour = 'CONTOSO\Steve' + $mockEnumMemberNames = @($mockSqlServerLoginOne,$mockSqlServerLoginTwo) + $mockSqlServerLoginType = 'WindowsUser' + $mockExpectedServerRoleToDrop = 'ServerRoleToDrop' + + # Default parameters that are used for the It-blocks + $mockDefaultParameters = @{ + SQLInstanceName = $mockSqlServerInstanceName + SQLServer = $mockSqlServerName + } + + #region Function mocks + + $mockConnectSQL = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name InstanceName -Value $mockSqlServerInstanceName -PassThru | + Add-Member -MemberType NoteProperty -Name ComputerNamePhysicalNetBIOS -Value $mockSqlServerName -PassThru | + Add-Member -MemberType ScriptProperty -Name Roles -Value { + return @{ + $mockSqlServerRole = ( New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlServerRole -PassThru | + Add-Member -MemberType ScriptMethod -Name EnumMemberNames -Value { + if ($mockInvalidOperationForEnumMethod) + { + throw 'Mock EnumMemberNames Method was called with invalid operation.' + } + else + { + $mockEnumMemberNames + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name Drop -Value { + if ($mockInvalidOperationForDropMethod) + { + throw 'Mock Drop Method was called with invalid operation.' + } + + if ( $this.Name -ne $mockExpectedServerRoleToDrop ) + { + throw "Called mocked drop() method without dropping the right server role. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedServerRoleToDrop, $this.Name + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name AddMember -Value { + if ($mockInvalidOperationForAddMemberMethod) + { + throw 'Mock AddMember Method was called with invalid operation.' + } + + if ( $mockSqlServerLoginToAdd -ne $mockExpectedMemberToAdd ) + { + throw "Called mocked AddMember() method without adding the right login. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedMemberToAdd, $mockSqlServerLoginToAdd + } + } -PassThru | + Add-Member -MemberType ScriptMethod -Name DropMember -Value { + if ($mockInvalidOperationForDropMemberMethod) + { + throw 'Mock DropMember Method was called with invalid operation.' + } + + if ( $mockSqlServerLoginToDrop -ne $mockExpectedMemberToDrop ) + { + throw "Called mocked DropMember() method without removing the right login. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedMemberToDrop, $mockSqlServerLoginToDrop + } + } -PassThru + ) + } + } -PassThru | + Add-Member -MemberType ScriptProperty -Name Logins -Value { + return @{ + $mockSqlServerLoginOne = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockSqlServerLoginType -PassThru + )) + $mockSqlServerLoginTwo = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockSqlServerLoginType -PassThru + )) + $mockSqlServerLoginTree = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockSqlServerLoginType -PassThru + )) + $mockSqlServerLoginFour = @(( + New-Object Object | + Add-Member -MemberType NoteProperty -Name LoginType -Value $mockSqlServerLoginType -PassThru + )) + } + } -PassThru -Force + ) + ) + } + + $mockNewObjectServerRole = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name Name -Value $mockSqlServerRoleAdd -PassThru | + Add-Member -MemberType ScriptMethod -Name Create -Value { + if ($mockInvalidOperationForCreateMethod) + { + throw 'Mock Create Method was called with invalid operation.' + } + + if ( $this.Name -ne $mockExpectedServerRoleToCreate ) + { + throw "Called mocked Create() method without adding the right server role. Expected '{0}'. But was '{1}'." ` + -f $mockExpectedServerRoleToCreate, $this.Name + } + } -PassThru -Force + ) + ) + } + #endregion + + Describe "MSFT_xSQLServerRole\Get-TargetResource" -Tag 'Get'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + } + + Context 'When the system is in the desired state and ensure is set to Absent' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = 'UnknownRoleName' + } + + It 'Should return the state as absent when the role does not exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the members as null' { + $result = Get-TargetResource @testParameters + $result.Members | Should Be $null + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Absent' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + } + + It 'Should not return the state as absent when the role exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Not Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the members as not null' { + $result = Get-TargetResource @testParameters + $result.Members | Should Not Be $null + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When passing values to parameters and throwing with EnumMemberNames method' { + It 'Should throw the correct error' { + $mockInvalidOperationForEnumMethod = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + } + + $throwInvalidOperation = ('Failed to enumerate members of the server role ' + ` + 'named AdminSqlforBI on localhost\MSSQLSERVER. InnerException: ' + ` + 'Exception calling "EnumMemberNames" with "0" argument(s): ' + ` + '"Mock EnumMemberNames Method was called with invalid operation."') + + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is in the desired state, parameter Members is assigned a value and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + Members = $mockEnumMemberNames + } + + It 'Should return the state as present when the members are correct' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the members as not null' { + $result = Get-TargetResource @testParameters + $result.Members | Should Be $testParameters.Members + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state, parameter MembersToInclude is assigned a value and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + MembersToInclude = $mockSqlServerLoginTwo + } + + It 'Should return the state as present when the correct members exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the members as not null' { + $result = Get-TargetResource @testParameters + $result.Members | Should Not Be $null + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + $result.MembersToInclude | Should Be $testParameters.MembersToInclude + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state, parameter MembersToExclude is assigned a value and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + MembersToExclude = $mockSqlServerLoginTree + } + + It 'Should return the state as present when the members does not exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Present' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + $result.MembersToExclude | Should Be $testParameters.MembersToExclude + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state, parameter MembersToInclude is assigned a value, parameter Members is assigned a value, and ensure is set to Present' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + Members = $mockEnumMemberNames + MembersToInclude = $mockSqlServerLoginTree + } + + $throwInvalidOperation = ('The parameter MembersToInclude and/or ' + ` + 'MembersToExclude must not be set, or be set ' + ` + 'to $null, when parameter Members are used.') + + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + + } + + Context 'When the system is not in the desired state, parameter MembersToExclude is assigned a value, parameter Members is assigned a value, and ensure is set to Present' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + Members = $mockEnumMemberNames + MembersToExclude = $mockSqlServerLoginTree + } + + $throwInvalidOperation = ('The parameter MembersToInclude and/or ' + ` + 'MembersToExclude must not be set, or be set ' + ` + 'to $null, when parameter Members are used.') + + { Get-TargetResource @testParameters } | Should Throw $throwInvalidOperation + } + + It 'Should call the mock function Connect-SQL' { + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope Context + } + } + + Context 'When the system is not in the desired state and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = 'UnknownRoleName' + } + + It 'Should return the state as absent when the role does not exist' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the members as null' { + $result = Get-TargetResource @testParameters + $result.Members | Should Be $null + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state, parameter Members is assigned a value and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + Members = @($mockSqlServerLoginOne,$mockSqlServerLoginTree) + } + + It 'Should return the state as absent when the members in the role are wrong' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the members as not null' { + $result = Get-TargetResource @testParameters + $result.Members | Should Not Be $null + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state, parameter MembersToInclude is assigned a value and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + MembersToInclude = $mockSqlServerLoginTree + } + + It 'Should return the state as absent when the members in the role are missing' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the members as not null' { + $result = Get-TargetResource @testParameters + $result.Members | Should Not Be $null + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + $result.MembersToInclude | Should Be $testParameters.MembersToInclude + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state, parameter MembersToExclude is assigned a value and ensure is set to Present' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + ServerRoleName = $mockSqlServerRole + MembersToExclude = $mockSqlServerLoginTwo + } + + It 'Should return the state as absent when the members in the role are present' { + $result = Get-TargetResource @testParameters + $result.Ensure | Should Be 'Absent' + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.SQLServer | Should Be $testParameters.SQLServer + $result.SQLInstanceName | Should Be $testParameters.SQLInstanceName + $result.ServerRoleName | Should Be $testParameters.ServerRoleName + $result.MembersToExclude | Should Be $testParameters.MembersToExclude + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "MSFT_xSQLServerRole\Test-TargetResource" -Tag 'Test'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + } + + Context 'When the system is not in the desired state and ensure is set to Absent' { + It 'Should return false when desired server role exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Absent' + ServerRoleName = $mockSqlServerRole + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state and ensure is set to Absent' { + It 'Should return true when desired server role does not exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Absent' + ServerRoleName = 'newServerRole' + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is in the desired state and ensure is set to Present' { + It 'Should return true when desired server role exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure parameter is set to Present' { + It 'Should return false when desired members are not in desired server role' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + Members = @($mockSqlServerLoginTree,$mockSqlServerLoginFour) + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When both the parameters Members and MembersToInclude are assigned a value and ensure is set to Present' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + Members = $mockEnumMemberNames + MembersToInclude = $mockSqlServerLoginTree + } + + $throwInvalidOperation = ('The parameter MembersToInclude and/or ' + ` + 'MembersToExclude must not be set, or be set ' + ` + 'to $null, when parameter Members are used.') + + { Test-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToInclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should return true when desired server role exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToInclude = $mockSqlServerLoginTwo + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToInclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should return false when desired server role does not exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToInclude = $mockSqlServerLoginTree + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When both the parameters Members and MembersToExclude are assigned a value and ensure is set to Present' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + Members = $mockEnumMemberNames + MembersToExclude = $mockSqlServerLoginTwo + } + + $throwInvalidOperation = ('The parameter MembersToInclude and/or ' + ` + 'MembersToExclude must not be set, or be set ' + ` + 'to $null, when parameter Members are used.') + + { Test-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToExclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should return true when desired server role does not exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToExclude = $mockSqlServerLoginTree + } + + $result = Test-TargetResource @testParameters + $result | Should Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToExclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should return false when desired server role exist' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToExclude = $mockSqlServerLoginTwo + } + + $result = Test-TargetResource @testParameters + $result | Should Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe "MSFT_xSQLServerRole\Set-TargetResource" -Tag 'Set'{ + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + Mock -CommandName New-Object -MockWith $mockNewObjectServerRole -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.ServerRole' + } + } + + Context 'When the system is not in the desired state and ensure is set to Absent' { + It 'Should not throw when calling the drop method' { + $mockSqlServerRole = 'ServerRoleToDrop' + $mockExpectedServerRoleToDrop = 'ServerRoleToDrop' + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Absent' + ServerRoleName = $mockSqlServerRole + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Absent' { + It 'Should throw the correct error when calling the drop method' { + $mockInvalidOperationForDropMethod = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Absent' + ServerRoleName = $mockSqlServerRole + } + + $throwInvalidOperation = ('Failed to drop the server role named AdminSqlforBI on ' + ` + 'localhost\MSSQLSERVER. InnerException: Exception calling ' + ` + '"Drop" with "0" argument(s): "Mock Drop Method ' + ` + 'was called with invalid operation."') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state and ensure is set to Present' { + It 'Should not throw when calling the create method' { + $mockSqlServerRoleAdd = 'ServerRoleToAdd' + $mockExpectedServerRoleToCreate = 'ServerRoleToAdd' + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRoleAdd + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.ServerRole' { + Assert-MockCalled New-Object -Exactly -Times 1 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.ServerRole' + } -Scope Context + } + } + + Context 'When the system is not in the desired state and ensure is set to Present' { + It 'Should throw the correct error when calling the create method' { + $mockSqlServerRoleAdd = 'ServerRoleToAdd' + $mockExpectedServerRoleToCreate = 'ServerRoleToAdd' + $mockInvalidOperationForCreateMethod = $true + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRoleAdd + } + + $throwInvalidOperation = ('Failed to create the server role named ServerRoleToAdd on ' + ` + 'localhost\MSSQLSERVER. InnerException: Exception calling ' + ` + '"Create" with "0" argument(s): "Mock Create Method ' + ` + 'was called with invalid operation."') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should call the mock function New-Object with TypeName equal to Microsoft.SqlServer.Management.Smo.ServerRole' { + Assert-MockCalled New-Object -Exactly -Times 1 -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.Smo.ServerRole' + } -Scope Context + } + } + + Context 'When both the parameters Members and MembersToInclude are assigned a value and ensure is set to Present' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + Members = $mockEnumMemberNames + MembersToInclude = $mockSqlServerLoginTree + } + + $throwInvalidOperation = ('The parameter MembersToInclude and/or ' + ` + 'MembersToExclude must not be set, or be set ' + ` + 'to $null, when parameter Members are used.') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When both the parameters Members and MembersToExclude are assigned a value and ensure is set to Present' { + It 'Should throw the correct error' { + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + Members = $mockEnumMemberNames + MembersToExclude = $mockSqlServerLoginTwo + } + + $throwInvalidOperation = ('The parameter MembersToInclude and/or ' + ` + 'MembersToExclude must not be set, or be set ' + ` + 'to $null, when parameter Members are used.') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToInclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should not thrown when calling the AddMember method' { + $mockExpectedMemberToAdd = $mockSqlServerLoginTree + $mockSqlServerLoginToAdd = $mockSqlServerLoginTree + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToInclude = $mockSqlServerLoginTree + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToInclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should throw the correct error when calling the AddMember method' { + $mockInvalidOperationForAddMemberMethod = $true + $mockExpectedMemberToAdd = $mockSqlServerLoginTree + $mockSqlServerLoginToAdd = $mockSqlServerLoginTree + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToInclude = $mockSqlServerLoginTree + } + + $throwInvalidOperation = ('Failed to add member CONTOSO\Lucy to the server role named AdminSqlforBI ' + ` + 'on localhost\MSSQLSERVER. InnerException: Exception calling "AddMember" ' + ` + 'with "1" argument(s): "Mock AddMember Method was called with invalid operation."') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToInclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should throw the correct error when login does not exist' { + $mockExpectedMemberToAdd = $mockSqlServerLoginTree + $mockSqlServerLoginToAdd = $mockSqlServerLoginTree + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToInclude = 'KingJulian' + } + + $throwInvalidOperation = ("Login 'KingJulian' does not exist on SQL server 'localhost\MSSQLSERVER'.") + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToExclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should not throw when calling the DropMember method' { + $mockExpectedMemberToAdd = $mockSqlServerLoginTwo + $mockSqlServerLoginToAdd = $mockSqlServerLoginTwo + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToExclude = $mockSqlServerLoginTwo + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToExclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should throw the correct error when calling the DropMember method' { + $mockInvalidOperationForDropMemberMethod = $true + $mockExpectedMemberToDrop = $mockSqlServerLoginTwo + $mockSqlServerLoginToDrop = $mockSqlServerLoginTwo + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToExclude = $mockSqlServerLoginTwo + } + + $throwInvalidOperation = ('Failed to drop member CONTOSO\Kelly to the server role named AdminSqlforBI ' + ` + 'on localhost\MSSQLSERVER. InnerException: Exception calling "DropMember" ' + ` + 'with "1" argument(s): "Mock DropMember Method was called with invalid operation."') + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter MembersToExclude is assigned a value, parameter Members is not assigned a value, and ensure is set to Present' { + It 'Should throw the correct error when login does not exist' { + $mockEnumMemberNames = @('KingJulian',$mockSqlServerLoginOne,$mockSqlServerLoginTwo) + $mockExpectedMemberToAdd = $mockSqlServerLoginTree + $mockSqlServerLoginToAdd = $mockSqlServerLoginTree + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + MembersToExclude = 'KingJulian' + } + + $throwInvalidOperation = ("Login 'KingJulian' does not exist on SQL server 'localhost\MSSQLSERVER'.") + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When parameter Members is assigned a value and ensure is set to Present' { + It 'Should throw the correct error when login does not exist' { + $mockExpectedMemberToAdd = $mockSqlServerLoginTree + $mockSqlServerLoginToAdd = $mockSqlServerLoginTree + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + Members = @('KingJulian',$mockSqlServerLoginOne,$mockSqlServerLoginTree) + } + + $throwInvalidOperation = ("Login 'KingJulian' does not exist on SQL server 'localhost\MSSQLSERVER'.") + + { Set-TargetResource @testParameters } | Should Throw $throwInvalidOperation + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When Members parameter is set and ensure parameter is set to Present' { + It 'Should not throw when calling both the AddMember and DropMember methods' { + $mockExpectedMemberToAdd = $mockSqlServerLoginTree + $mockSqlServerLoginToAdd = $mockSqlServerLoginTree + $mockExpectedMemberToDrop = $mockSqlServerLoginTwo + $mockSqlServerLoginToDrop = $mockSqlServerLoginTwo + $testParameters = $mockDefaultParameters + $testParameters += @{ + Ensure = 'Present' + ServerRoleName = $mockSqlServerRole + Members = @($mockSqlServerLoginOne,$mockSqlServerLoginTree) + } + + { Set-TargetResource @testParameters } | Should Not Throw + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + } +} +finally +{ + Invoke-TestCleanup +} + diff --git a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 index ebb0b6d2d..37e8720c6 100644 --- a/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerScript.Tests.ps1 @@ -44,7 +44,6 @@ try InModuleScope 'MSFT_xSQLServerScript' { $script:DSCModuleName = 'xSQLServer' $resourceName = 'MSFT_xSQLServerScript' - $sqlServerHelperModuleName = 'xSQLServerHelper' $testParameters = @{ ServerInstance = $env:COMPUTERNAME @@ -58,9 +57,9 @@ try Context 'Get-TargetResource fails to import SQLPS module' { $throwMessage = "Failed to import SQLPS module." - Mock -CommandName Import-Module -MockWith { + Mock -CommandName Import-SQLPSModule -MockWith { throw $throwMessage - } -ModuleName $sqlServerHelperModuleName + } It 'Should throw the correct error from Import-Module' { { Get-TargetResource @testParameters } | Should Throw $throwMessage @@ -68,7 +67,7 @@ try } Context 'Get-TargetResource returns script results successfully' { - Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-SQLPSModule Mock -CommandName Invoke-Sqlcmd -MockWith { return '' } @@ -86,7 +85,7 @@ try 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 Import-SQLPSModule Mock -CommandName Invoke-Sqlcmd -MockWith { throw $errorMessage } @@ -102,7 +101,7 @@ try Context 'Set-TargetResource fails to import SQLPS module' { $throwMessage = "Failed to import SQLPS module." - Mock -CommandName Import-Module -MockWith { throw $throwMessage } -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-SQLPSModule -MockWith { throw $throwMessage } It 'Should throw the correct error from Import-Module' { { Set-TargetResource @testParameters } | Should Throw $throwMessage @@ -110,7 +109,7 @@ try } Context 'Set-TargetResource runs script without issue' { - Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith { return '' } @@ -124,7 +123,7 @@ try 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 Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith { throw $errorMessage } @@ -139,9 +138,9 @@ try Context 'Test-TargetResource fails to import SQLPS module' { $throwMessage = 'Failed to import SQLPS module.' - Mock -CommandName Import-Module -MockWith { + Mock -CommandName Import-SQLPSModule -MockWith { throw $throwMessage - } -ModuleName $sqlServerHelperModuleName + } It 'Should throw the correct error from Import-Module' { { Set-TargetResource @testParameters } | Should Throw $throwMessage @@ -149,7 +148,7 @@ try } Context 'Test-TargetResource runs script without issue' { - Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith {} It 'Should return true' { @@ -159,7 +158,7 @@ try } Context 'Test-TargetResource throws the exception SqlPowerShellSqlExecutionException when running the script in the TestFilePath parameter' { - Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith { throw New-Object Microsoft.SqlServer.Management.PowerShell.SqlPowerShellSqlExecutionException } @@ -173,7 +172,7 @@ try 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 Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith { throw $errorMessage } @@ -193,9 +192,9 @@ try Context 'Invoke-SqlScript fails to import SQLPS module' { $throwMessage = "Failed to import SQLPS module." - Mock -CommandName Import-Module -MockWith { + Mock -CommandName Import-SQLPSModule -MockWith { throw $throwMessage - } -ModuleName $sqlServerHelperModuleName + } It 'Should throw the correct error from Import-Module' { { Invoke-SqlScript @invokeScriptParameters } | Should Throw $throwMessage @@ -206,7 +205,7 @@ try $passwordPlain = "password" $user = "User" - Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith {} -ParameterFilter { ($Username -eq $user) -and ($Password -eq $passwordPlain) } @@ -227,7 +226,7 @@ try Context 'Invoke-SqlScript fails to execute the SQL scripts' { $errorMessage = "Failed to run SQL Script" - Mock -CommandName Import-Module -MockWith {} -ModuleName $sqlServerHelperModuleName + Mock -CommandName Import-SQLPSModule -MockWith {} Mock -CommandName Invoke-Sqlcmd -MockWith { throw $errorMessage } diff --git a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 index e5332184c..4043fc0ed 100644 --- a/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 +++ b/Tests/Unit/MSFT_xSQLServerSetup.Tests.ps1 @@ -39,6 +39,7 @@ try InModuleScope $script:DSCResourceName { # Testing each supported SQL Server version $testProductVersion = @( + 14, # SQL Server "2017" 13, # SQL Server 2016 12, # SQL Server 2014 11, # SQL Server 2012 @@ -106,6 +107,9 @@ try $mockAgentServiceAccount = 'COMPANY\AgentAccount' $mockAgentServicePassword = 'Ag3ntP@ssw0rd' $mockSQLAgentCredential = New-Object System.Management.Automation.PSCredential($mockAgentServiceAccount,($mockAgentServicePassword | ConvertTo-SecureString -AsPlainText -Force)) + $mockAnalysisServiceAccount = 'COMPANY\AnalysisAccount' + $mockAnslysisServicePassword = 'Analysiss3v!c3P@ssw0rd' + $mockAnalysisServiceCredential = New-Object System.Management.Automation.PSCredential($mockAnalysisServiceAccount,($mockSQLServicePassword | ConvertTo-SecureString -AsPlainText -Force)) $mockClusterNodes = @($env:COMPUTERNAME,'SQL01','SQL02') @@ -398,11 +402,30 @@ try ) } + $mockGetItemProperty_DQFeature = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'DQ_Components' -Value 1 -PassThru -Force + ) + ) + } + + $mockGetItemProperty_BOLandDQCFeature = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'SQL_BOL_Components' -Value 1 -PassThru -Force | + Add-Member -MemberType NoteProperty -Name 'SQL_DQ_CLIENT_Full' -Value 1 -PassThru -Force + ) + ) + } + $mockGetItemProperty_ClientComponentsFull_FeatureList = { return @( ( New-Object Object | - Add-Member -MemberType NoteProperty -Name 'FeatureList' -Value 'Connectivity_Full=3 SQL_SSMS_Full=3 Tools_Legacy_Full=3 Connectivity_FNS=3 SQL_Tools_Standard_FNS=3 Tools_Legacy_FNS=3' -PassThru -Force + Add-Member -MemberType NoteProperty -Name 'FeatureList' -Value 'Connectivity_Full=3 SQL_SSMS_Full=3 Tools_Legacy_Full=3 Connectivity_FNS=3 SQL_Tools_Standard_FNS=3 Tools_Legacy_FNS=3 SDK_Full=3 SDK_FNS=3' -PassThru -Force ) ) } @@ -837,13 +860,17 @@ try } # Start by checking whether we have the same number of parameters - Write-Verbose 'Verifying argument count (expected vs actual)' -Verbose - $mockStartWin32ProcessExpectedArgument.Keys.Count | Should BeExactly $argumentHashTable.Keys.Count + Write-Verbose 'Verifying setup argument count (expected vs actual)' -Verbose + Write-Verbose -Message ('Expected: {0}' -f ($mockStartWin32ProcessExpectedArgument.Keys -join ',') ) -Verbose + Write-Verbose -Message ('Actual: {0}' -f ($argumentHashTable.Keys -join ',')) -Verbose + $argumentHashTable.Keys.Count | Should BeExactly $mockStartWin32ProcessExpectedArgument.Keys.Count + + Write-Verbose 'Verifying actual setup arguments against expected setup arguments' -Verbose foreach ($argumentKey in $mockStartWin32ProcessExpectedArgument.Keys) { - $argumentPassed = $argumentHashTable.ContainsKey($argumentKey) - $argumentPassed | Should Be $true + $argumentKeyName = $argumentHashTable.GetEnumerator() | Where-Object -FilterScript { $_.Name -eq $argumentKey } | Select-Object -ExpandProperty Name + $argumentKeyName | Should Be $argumentKey $argumentValue = $argumentHashTable.$argumentKey $argumentValue | Should Be $mockStartWin32ProcessExpectedArgument.$argumentKey @@ -860,9 +887,11 @@ try These are written with both lower-case and upper-case to make sure we support that. The feature list must be written in the order it is returned by the function Get-TargerResource. #> - Features = 'SQLEngine,Replication,Conn,Bc,FullText,Rs,As,Is,Ssms,Adv_Ssms' + Features = 'SQLEngine,Replication,Dqc,Dq,Bol,Conn,Bc,Sdk,FullText,Rs,As,Is,Ssms,Adv_Ssms' } + $featuresForSqlServer2016 = '' + $mockDefaultClusterParameters = @{ SetupCredential = $mockSetupCredential @@ -905,6 +934,7 @@ try 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' + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\30AE1F084B1CF8B4797ECB3CCAA3B3B6' } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable Mock -CommandName Get-Item -ParameterFilter { @@ -950,7 +980,8 @@ try SourcePath = $mockSourcePath } - if ($mockSqlMajorVersion -eq 13) { + if ($mockSqlMajorVersion -in (13,14)) + { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or @@ -960,7 +991,9 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable - } else { + } + else + { 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 @@ -979,6 +1012,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -1062,7 +1103,8 @@ try SourcePath = $mockSourcePathUNC } - if ($mockSqlMajorVersion -eq 13) { + if ($mockSqlMajorVersion -in (13,14)) + { # Mock all SSMS products here to make sure we don't return any when testing SQL Server 2016 Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or @@ -1072,7 +1114,9 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable - } else { + } + else + { 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 @@ -1091,6 +1135,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -1164,7 +1216,7 @@ try } } - Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for features 'CONN' and 'BC'" { + Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for features 'CONN', 'SDK' and 'BC'" { BeforeEach { $testParameters = $mockDefaultParameters.Clone() $testParameters.Remove('Features') @@ -1174,21 +1226,24 @@ try SourcePath = $mockSourcePath } - if ($mockSqlMajorVersion -eq 10) { + 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) { + 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) { + 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) @@ -1240,6 +1295,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -1251,11 +1314,13 @@ try It 'Should return correct names of installed features' { $result = Get-TargetResource @testParameters - if ($mockSqlMajorVersion -eq 13) + if ($mockSqlMajorVersion -in (13,14)) + { + $result.Features | Should Be 'SQLENGINE,REPLICATION,DQC,DQ,BOL,FULLTEXT,RS,AS,IS' + } + else { - $result.Features | Should Be 'SQLENGINE,REPLICATION,FULLTEXT,RS,AS,IS' - } else { - $result.Features | Should Be 'SQLENGINE,REPLICATION,FULLTEXT,RS,AS,IS,SSMS,ADV_SSMS' + $result.Features | Should Be 'SQLENGINE,REPLICATION,DQC,DQ,BOL,FULLTEXT,RS,AS,IS,SSMS,ADV_SSMS' } } } @@ -1270,21 +1335,24 @@ try SourcePath = $mockSourcePath } - if ($mockSqlMajorVersion -eq 10) { + 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) { + 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) { + 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) @@ -1336,6 +1404,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -1362,7 +1438,8 @@ try $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' } -Exactly -Times 1 -Scope It - if ($mockSqlMajorVersion -eq 13) { + if ($mockSqlMajorVersion -in (13,14)) + { 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 @@ -1371,7 +1448,9 @@ try $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 { + } + 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 @@ -1417,11 +1496,13 @@ try It 'Should return correct names of installed features' { $result = Get-TargetResource @testParameters - if ($mockSqlMajorVersion -eq 13) + if ($mockSqlMajorVersion -in (13,14)) { $featuresForSqlServer2016 = (($mockDefaultParameters.Features.ToUpper()) -replace 'SSMS,','') -replace ',ADV_SSMS','' $result.Features | Should Be $featuresForSqlServer2016 - } else { + } + else + { $result.Features | Should Be $mockDefaultParameters.Features.ToUpper() } } @@ -1466,21 +1547,24 @@ try SourcePath = $mockSourcePathUNC } - if ($mockSqlMajorVersion -eq 10) { + 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) { + 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) { + 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) @@ -1532,6 +1616,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -1558,7 +1650,8 @@ try $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockDefaultInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' } -Exactly -Times 1 -Scope It - if ($mockSqlMajorVersion -eq 13) { + if ($mockSqlMajorVersion -in (13,14)) + { 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 @@ -1567,7 +1660,9 @@ try $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 { + } + 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 @@ -1613,11 +1708,13 @@ try It 'Should return correct names of installed features' { $result = Get-TargetResource @testParameters - if ($mockSqlMajorVersion -eq 13) + if ($mockSqlMajorVersion -in (13,14)) { $featuresForSqlServer2016 = (($mockDefaultParameters.Features.ToUpper()) -replace 'SSMS,','') -replace ',ADV_SSMS','' $result.Features | Should Be $featuresForSqlServer2016 - } else { + } + else + { $result.Features | Should Be $mockDefaultParameters.Features.ToUpper() } } @@ -1671,7 +1768,8 @@ try SourcePath = $mockSourcePath } - if ($mockSqlMajorVersion -eq 13) { + if ($mockSqlMajorVersion -in (13,14)) + { # Mock this here to make sure we don't return any older components (<=2014) when testing SQL Server 2016 Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudio2008R2_ProductIdentifyingNumber) -or @@ -1681,7 +1779,9 @@ try $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2012_ProductIdentifyingNumber) -or $Path -eq (Join-Path -Path $mockRegistryUninstallProductsPath -ChildPath $mockSqlServerManagementStudioAdvanced2014_ProductIdentifyingNumber) } -MockWith $mockGetItemProperty_UninstallProducts -Verifiable - } else { + } + else + { 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 @@ -1698,6 +1798,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -1779,21 +1887,24 @@ try SourcePath = $mockSourcePath } - if ($mockSqlMajorVersion -eq 10) { + 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) { + 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) { + 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) @@ -1843,6 +1954,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -1867,7 +1986,8 @@ try $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockNamedInstance_InstanceId\Setup" -and $Name -eq 'SqlProgramDir' } -Exactly -Times 1 -Scope It - if ($mockSqlMajorVersion -eq 13) { + if ($mockSqlMajorVersion -in (13,14)) + { 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 @@ -1876,7 +1996,9 @@ try $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 { + } + 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 @@ -1922,11 +2044,13 @@ try It 'Should return correct names of installed features' { $result = Get-TargetResource @testParameters - if ($mockSqlMajorVersion -eq 13) + if ($mockSqlMajorVersion -in (13,14)) { $featuresForSqlServer2016 = (($mockDefaultParameters.Features.ToUpper()) -replace 'SSMS,','') -replace ',ADV_SSMS','' $result.Features | Should Be $featuresForSqlServer2016 - } else { + } + else + { $result.Features | Should Be $mockDefaultParameters.Features.ToUpper() } } @@ -2083,6 +2207,7 @@ try 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' + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\30AE1F084B1CF8B4797ECB3CCAA3B3B6' } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable Mock -CommandName Get-Item -ParameterFilter { @@ -2137,6 +2262,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -2384,6 +2517,32 @@ try $result | Should Be $false } + + # This is a test for regression testing of issue #432 + It 'Should return false if a SQL Server failover cluster is missing features' { + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = 'SQLENGINE' # Must be upper-case since Get-TargetResource returns upper-case. + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + } -Verifiable + + $testClusterParameters = $testParameters.Clone() + $testClusterParameters['Features'] = 'SQLEngine,AS' + + $testClusterParameters += @{ + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + + $result = Test-TargetResource @testClusterParameters + $result | Should Be $false + } } # For this test we only need to test one SQL Server version. Mocking SQL Server 2014 for the 'in the desired state' test. @@ -2466,6 +2625,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -Verifiable + Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" } -MockWith $mockGetItemProperty_ClientComponentsFull_FeatureList -Verifiable @@ -2596,6 +2763,32 @@ try $result | Should Be $true } + + # This is a test for regression testing of issue #432 + It 'Should return true if a SQL Server failover cluster has all features and is in desired state' { + $mockCurrentInstanceName = $mockDefaultInstance_InstanceName + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + Features = 'SQLENGINE,AS' # Must be upper-case since Get-TargetResource returns upper-case. + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + } -Verifiable + + $testClusterParameters = $testParameters.Clone() + $testClusterParameters['Features'] = 'SQLEngine,AS' + + $testClusterParameters += @{ + FailoverClusterGroupName = $mockDefaultInstance_FailoverClusterGroupName + FailoverClusterIPAddress = $mockDefaultInstance_FailoverClusterIPAddress + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName + } + + $result = Test-TargetResource @testClusterParameters + $result | Should Be $true + } } Assert-VerifiableMocks @@ -2664,27 +2857,8 @@ try $Name -eq 'ImagePath' } -MockWith $mockGetItemProperty_ServicesAnalysis -Verifiable - # Mocking SharedDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable - - Mock -CommandName Get-Item -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' - } -MockWith $mockGetItem_SharedDirectory -Verifiable - - # Mocking SharedWowDirectory - Mock -CommandName Get-ItemProperty -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable - - Mock -CommandName Get-Item -ParameterFilter { - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or - $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' - } -MockWith $mockGetItem_SharedWowDirectory -Verifiable + # Mocking SharedDirectory and SharedWowDirectory (when not previously installed) + Mock -CommandName Get-ItemProperty -Verifiable Mock -CommandName Get-ItemProperty -ParameterFilter { $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\Tools\Setup\Client_Components_Full" @@ -2711,20 +2885,6 @@ try Context "When SQL Server version is $mockSqlMajorVersion and the system is not in the desired state for a default instance" { BeforeEach { - $testParameters = $mockDefaultParameters.Clone() - $testParameters += @{ - InstanceName = $mockDefaultInstance_InstanceName - SourceCredential = $null - SourcePath = $mockSourcePath - ProductKey = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' - SQLSysAdminAccounts = 'COMPANY\User1','COMPANY\SQLAdmins' - } - - if ( $mockSqlMajorVersion -eq 13 ) - { - $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' - } - Mock -CommandName New-SmbMapping -Verifiable Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Start-Process -Verifiable @@ -2745,6 +2905,25 @@ try } It 'Should set the system in the desired state when feature is SQLENGINE' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + ProductKey = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' + SQLSysAdminAccounts = 'COMPANY\User1','COMPANY\SQLAdmins' + ASSysAdminAccounts = 'COMPANY\User1','COMPANY\SQLAdmins' + InstanceDir = 'D:' + InstallSQLDataDir = 'E:' + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' + } + + if ( $mockSqlMajorVersion -in (13,14) ) + { + $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' + } + $mockStartWin32ProcessExpectedArgument = @{ Quiet = 'True' IAcceptSQLServerLicenseTerms = 'True' @@ -2753,8 +2932,12 @@ try InstanceName = 'MSSQLSERVER' Features = $testParameters.Features SQLSysAdminAccounts = 'COMPANY\sqladmin COMPANY\SQLAdmins COMPANY\User1' - ASSysAdminAccounts = 'COMPANY\sqladmin' + ASSysAdminAccounts = 'COMPANY\sqladmin COMPANY\SQLAdmins COMPANY\User1' PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' + InstanceDir = 'D:\' + InstallSQLDataDir = 'E:\' + InstallSharedDir = 'C:\Program Files\Microsoft SQL Server' + InstallSharedWOWDir = 'C:\Program Files (x86)\Microsoft SQL Server' } { Set-TargetResource @testParameters } | Should Not Throw @@ -2787,8 +2970,16 @@ try Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } - if( $mockSqlMajorVersion -eq 13 ) { + if( $mockSqlMajorVersion -in (13,14) ) + { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + $testParameters.Features = 'SSMS' $mockStartWin32ProcessExpectedArgument = @{} @@ -2796,13 +2987,29 @@ try } It 'Should throw when feature parameter contains ''ADV_SSMS'' when installing SQL Server 2016' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + $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 { + } + else + { It 'Should set the system in the desired state when feature is SSMS' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + $testParameters.Features = 'SSMS' $mockStartWin32ProcessExpectedArgument = @{ @@ -2811,7 +3018,6 @@ try Action = 'Install' InstanceName = 'MSSQLSERVER' Features = 'SSMS' - PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' } { Set-TargetResource @testParameters } | Should Not Throw @@ -2840,6 +3046,13 @@ try } It 'Should set the system in the desired state when feature is ADV_SSMS' { + $testParameters = $mockDefaultParameters.Clone() + $testParameters += @{ + InstanceName = $mockDefaultInstance_InstanceName + SourceCredential = $null + SourcePath = $mockSourcePath + } + $testParameters.Features = 'ADV_SSMS' $mockStartWin32ProcessExpectedArgument = @{ @@ -2848,7 +3061,6 @@ try Action = 'Install' InstanceName = 'MSSQLSERVER' Features = 'ADV_SSMS' - PID = '1FAKE-2FAKE-3FAKE-4FAKE-5FAKE' } { Set-TargetResource @testParameters } | Should Not Throw @@ -2888,11 +3100,24 @@ try SourcePath = $mockSourcePathUNC } - if ( $mockSqlMajorVersion -eq 13 ) + if ( $mockSqlMajorVersion -in (13,14) ) { $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' } + # Mocking SharedDirectory (when previously installed and should not be installed again). + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\0D1F366D0FE0E404F8C15EE4F1C15094' -or + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\FEE2E540D20152D4597229B6CFBC0A69' + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\30AE1F084B1CF8B4797ECB3CCAA3B3B6' + } -MockWith $mockGetItemProperty_SharedDirectory -Verifiable + + # Mocking SharedWowDirectory (when previously installed and should not be installed again). + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\C90BFAC020D87EA46811C836AD3C507F' -or + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\A79497A344129F64CA7D69C56F5DD8B4' + } -MockWith $mockGetItemProperty_SharedWowDirectory -Verifiable + Mock -CommandName New-SmbMapping -Verifiable Mock -CommandName Remove-SmbMapping -Verifiable Mock -CommandName Start-Process -Verifiable @@ -2955,7 +3180,8 @@ try Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } - if( $mockSqlMajorVersion -eq 13 ) { + if( $mockSqlMajorVersion -in (13,14) ) + { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'SSMS' $mockStartWin32ProcessExpectedArgument = '' @@ -2969,7 +3195,9 @@ try { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } - } else { + } + else + { It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' @@ -3051,9 +3279,11 @@ try InstanceName = $mockDefaultInstance_InstanceName SourceCredential = $mockSetupCredential SourcePath = $mockSourcePathUNCWithoutLeaf + ForceReboot = $true + SuppressReboot = $true } - if ( $mockSqlMajorVersion -eq 13 ) + if ( $mockSqlMajorVersion -in (13,14) ) { $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' } @@ -3119,7 +3349,8 @@ try Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } - if( $mockSqlMajorVersion -eq 13 ) { + if( $mockSqlMajorVersion -in (13,14) ) + { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = 'SSMS' $mockStartWin32ProcessExpectedArgument = @{} @@ -3133,7 +3364,9 @@ try { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } - } else { + } + else + { It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' @@ -3224,9 +3457,10 @@ try InstanceName = $mockNamedInstance_InstanceName SourceCredential = $null SourcePath = $mockSourcePath + ForceReboot = $true } - if ( $mockSqlMajorVersion -eq 13 ) + if ( $mockSqlMajorVersion -in (13,14) ) { $testParameters.Features = $testParameters.Features -replace ',SSMS,ADV_SSMS','' } @@ -3282,7 +3516,8 @@ try Assert-MockCalled -CommandName Test-TargetResource -Exactly -Times 1 -Scope It } - if( $mockSqlMajorVersion -eq 13 ) { + if( $mockSqlMajorVersion -in (13,14) ) + { It 'Should throw when feature parameter contains ''SSMS'' when installing SQL Server 2016' { $testParameters.Features = $($testParameters.Features), 'SSMS' -join ',' $mockStartWin32ProcessExpectedArgument = @{} @@ -3296,7 +3531,9 @@ try { Set-TargetResource @testParameters } | Should Throw "'ADV_SSMS' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information." } - } else { + } + else + { It 'Should set the system in the desired state when feature is SSMS' { $testParameters.Features = 'SSMS' @@ -3374,24 +3611,17 @@ try Context "When SQL Server version is $mockSQLMajorVersion and the system is not in the desired state and the action is AddNode" { BeforeAll { $testParameters = $mockDefaultClusterParameters.Clone() - + $testParameters['Features'] += 'AS' $testParameters += @{ InstanceName = 'MSSQLSERVER' SourcePath = $mockSourcePath Action = 'AddNode' AgtSvcAccount = $mockSQLAgentCredential SqlSvcAccount = $mockSQLServiceCredential + ASSvcAccount = $mockAnalysisServiceCredential + FailoverClusterNetworkName = $mockDefaultInstance_FailoverClusterNetworkName } - $testParameters.Remove('Features') - $testParameters.Remove('SQLUserDBDir') - $testParameters.Remove('SQLUserDBLogDir') - $testParameters.Remove('SQLTempDbDir') - $testParameters.Remove('SQLTempDBlogDir') - - } - - BeforeAll { Mock -CommandName Get-CimInstance -MockWith $mockGetCimInstance_MSClusterResourceGroup_AvailableStorage -ParameterFilter { $Filter -eq "Name = 'Available Storage'" } -Verifiable @@ -3434,7 +3664,9 @@ try AgtSvcPassword = $mockSqlAgentCredential.GetNetworkCredential().Password SqlSvcAccount = $mockSqlServiceAccount SqlSvcPassword = $mockSQLServiceCredential.GetNetworkCredential().Password - + AsSvcAccount = $mockAnalysisServiceAccount + AsSvcPassword = $mockAnalysisServiceCredential.GetNetworkCredential().Password + SkipRules = 'Cluster_VerifyForErrors' } { Set-TargetResource @testParameters } | Should Not Throw @@ -3512,6 +3744,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -3803,6 +4043,14 @@ try $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\$($mockSqlMajorVersion)0\DQ\*" + } -MockWith $mockGetItemProperty_DQFeature -Verifiable + + Mock -CommandName Get-ItemProperty -ParameterFilter { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($mockSqlMajorVersion)0\ConfigurationState" + } -MockWith $mockGetItemProperty_BOLandDQCFeature -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 @@ -3830,13 +4078,15 @@ try } -Verifiable } - It 'Should add the SkipRules parameter to the installation arguments' { + It 'Should pass correct arguments to the setup process' { $mockStartWin32ProcessExpectedArgument = $mockStartWin32ProcessExpectedArgumentClusterDefault.Clone() $mockStartWin32ProcessExpectedArgument += @{ Action = 'PrepareFailoverCluster' SkipRules = 'Cluster_VerifyForErrors' } + $mockStartWin32ProcessExpectedArgument.Remove('FailoverClusterGroup') + $mockStartWin32ProcessExpectedArgument.Remove('SQLSysAdminAccounts') { Set-TargetResource @testParameters } | Should Not throw @@ -4298,6 +4548,18 @@ try } } } + + Describe 'Get-TemporaryFolder' -Tag 'Helper' { + BeforeAll { + $mockExpectedTempPath = [IO.Path]::GetTempPath() + } + + Context "When using Get-TemporaryFolder" { + It "Should return the correct temporary path." { + Get-TemporaryFolder | Should BeExactly $mockExpectedTempPath + } + } + } } } finally diff --git a/Tests/Unit/MSFT_xWaitForAvailabilityGroup.Tests.ps1 b/Tests/Unit/MSFT_xWaitForAvailabilityGroup.Tests.ps1 new file mode 100644 index 000000000..06b60b899 --- /dev/null +++ b/Tests/Unit/MSFT_xWaitForAvailabilityGroup.Tests.ps1 @@ -0,0 +1,244 @@ +$script:DSCModuleName = 'xSQLServer' +$script:DSCResourceName = 'MSFT_xWaitForAvailabilityGroup' + +#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 { + $mockClusterGroupName = 'AGTest' + $mockRetryInterval = 1 + $mockRetryCount = 2 + + $mockOtherClusterGroupName = 'UnknownAG' + + # Function stub of Get-ClusterGroup (when we do not have Failover Cluster powershell module available) + function Get-ClusterGroup { + param + ( + # Will contain the cluster group name so mock can bind filters on it. + [Parameter()] + [System.String] + $Name + ) + + throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand + } + + $mockGetClusterGroup = { + if ($Name -ne $mockExpectedClusterGroupName) + { + throw ('Mock Get-ClusterGroup called with unexpected name. Expected ''{0}'', but was ''{1}''' -f $mockExpectedClusterGroupName, $Name) + } + + return New-Object -TypeName PSObject -Property @{ + Name = $Name + } + } + + $mockGetClusterGroup_ParameterFilter_KnownGroup = { + $Name -eq $mockClusterGroupName + } + + $mockGetClusterGroup_ParameterFilter_UnknownGroup = { + $Name -eq $mockOtherClusterGroupName + } + + # Default parameters that are used for the It-blocks + $mockDefaultParameters = @{ + Name = $mockClusterGroupName + RetryIntervalSec = $mockRetryInterval + RetryCount = $mockRetryCount + } + + Describe 'MSFT_xWaitForAvailabilityGroup\Get-TargetResource' -Tag 'Get' { + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Get-ClusterGroup -MockWith $mockGetClusterGroup -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup -Verifiable + Mock -CommandName Get-ClusterGroup -MockWith { + return $null + } -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup -Verifiable + } + + Context 'When the system is in the desired state' { + $mockExpectedClusterGroupName = $mockClusterGroupName + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.RetryIntervalSec | Should -Be $mockRetryInterval + $result.RetryCount | Should -Be $mockRetryCount + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup ` + -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup ` + -Exactly -Times 0 -Scope It + } + + It 'Should return that the group exist' { + $result = Get-TargetResource @testParameters + $result.GroupExist | Should -Be $true + } + } + + Context 'When the system is not in the desired state' { + BeforeEach { + $testParameters.Name = $mockOtherClusterGroupName + } + + $mockExpectedClusterGroupName = $mockOtherClusterGroupName + + It 'Should return the same values as passed as parameters' { + $result = Get-TargetResource @testParameters + $result.RetryIntervalSec | Should -Be $mockRetryInterval + $result.RetryCount | Should -Be $mockRetryCount + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup ` + -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup ` + -Exactly -Times 1 -Scope It + } + + It 'Should return that the group does not exist' { + $result = Get-TargetResource @testParameters + $result.GroupExist | Should -Be $false + } + } + + Assert-VerifiableMocks + } + + + Describe 'MSFT_xWaitForAvailabilityGroup\Test-TargetResource' -Tag 'Test'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Get-ClusterGroup -MockWith $mockGetClusterGroup -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup -Verifiable + Mock -CommandName Get-ClusterGroup -MockWith { + return $null + } -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup -Verifiable + } + + Context 'When the system is in the desired state' { + $mockExpectedClusterGroupName = $mockClusterGroupName + + It 'Should return that desired state is present ($true)' { + $result = Test-TargetResource @testParameters + $result | Should -Be $true + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup ` + -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup ` + -Exactly -Times 0 -Scope It + } + } + + Context 'When the system is not in the desired state' { + $mockExpectedClusterGroupName = $mockOtherClusterGroupName + + It 'Should return that desired state is absent ($false)' { + $testParameters.Name = $mockOtherClusterGroupName + + $result = Test-TargetResource @testParameters + $result | Should -Be $false + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup ` + -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup ` + -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + + Describe 'MSFT_xWaitForAvailabilityGroup\Set-TargetResource' -Tag 'Set'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Start-Sleep + Mock -CommandName Get-ClusterGroup -MockWith $mockGetClusterGroup -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup -Verifiable + Mock -CommandName Get-ClusterGroup -MockWith { + return $null + } -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup -Verifiable + } + + Context 'When the system is in the desired state' { + $mockExpectedClusterGroupName = $mockClusterGroupName + + It 'Should find the cluster group and return without throwing' { + { Set-TargetResource @testParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup ` + -Exactly -Times 1 -Scope It + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup ` + -Exactly -Times 0 -Scope It } + } + + Context 'When the system is not in the desired state' { + $mockExpectedClusterGroupName = $mockOtherClusterGroupName + + It 'Should throw the correct error message' { + $testParameters.Name = $mockOtherClusterGroupName + + { Set-TargetResource @testParameters } | Should -Throw 'Cluster group UnknownAG not found after 2 attempts with 1 sec interval' + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_KnownGroup ` + -Exactly -Times 0 -Scope It + + Assert-MockCalled -CommandName Get-ClusterGroup ` + -ParameterFilter $mockGetClusterGroup_ParameterFilter_UnknownGroup ` + -Exactly -Times 2 -Scope It } + } + + Assert-VerifiableMocks + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/Stubs/SMO.cs b/Tests/Unit/Stubs/SMO.cs index 1d1745749..3025cab5d 100644 --- a/Tests/Unit/Stubs/SMO.cs +++ b/Tests/Unit/Stubs/SMO.cs @@ -18,10 +18,10 @@ public enum LoginCreateOptions IsHashed = 1, MustChange = 2 } - + // TypeName: Microsoft.SqlServer.Management.Smo.LoginType // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase - // Used by: + // Used by: // MSFT_xSQLServerLogin public enum LoginType { @@ -32,11 +32,11 @@ public enum LoginType SqlLogin = 2, WindowsGroup = 1, WindowsUser = 0, - Unknown = -1 // Added for verification (mock) purposes, to verify that a login type is passed + Unknown = -1 // Added for verification (mock) purposes, to verify that a login type is passed } - + // TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode - // Used by: + // Used by: // MSFT_xSQLAOGroupEnsure.Tests public enum AvailabilityReplicaFailoverMode { @@ -46,7 +46,7 @@ public enum AvailabilityReplicaFailoverMode } // TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode - // Used by: + // Used by: // MSFT_xSQLAOGroupEnsure.Tests public enum AvailabilityReplicaAvailabilityMode { @@ -55,6 +55,62 @@ public enum AvailabilityReplicaAvailabilityMode Unknown } + // TypeName: Microsoft.SqlServer.Management.Smo.EndpointType + // Used by: + // xSQLServerEndpoint + public enum EndpointType + { + DatabaseMirroring, + ServiceBroker, + Soap, + TSql + } + + // TypeName: Microsoft.SqlServer.Management.Smo.ProtocolType + // Used by: + // xSQLServerEndpoint + public enum ProtocolType + { + Http, + NamedPipes, + SharedMemory, + Tcp, + Via + } + + // TypeName: Microsoft.SqlServer.Management.Smo.ServerMirroringRole + // Used by: + // xSQLServerEndpoint + public enum ServerMirroringRole + { + All, + None, + Partner, + Witness + } + + // TypeName: Microsoft.SqlServer.Management.Smo.EndpointEncryption + // Used by: + // xSQLServerEndpoint + public enum EndpointEncryption + { + Disabled, + Required, + Supported + } + + // TypeName: Microsoft.SqlServer.Management.Smo.EndpointEncryptionAlgorithm + // Used by: + // xSQLServerEndpoint + public enum EndpointEncryptionAlgorithm + { + Aes, + AesRC4, + None, + RC4, + RC4Aes + } + #endregion Public Enums #region Public Classes @@ -67,52 +123,52 @@ public class Globals // Typename: Microsoft.SqlServer.Management.Smo.ObjectPermissionSet // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase - // Used by: + // Used by: // xSQLServerEndpointPermission.Tests.ps1 - public class ObjectPermissionSet + public class ObjectPermissionSet { public ObjectPermissionSet(){} public ObjectPermissionSet( bool connect ) { - this.Connect = connect; - } - + this.Connect = connect; + } + public bool Connect = false; } - + // TypeName: Microsoft.SqlServer.Management.Smo.ServerPermissionSet // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase - // Used by: + // Used by: // xSQLServerPermission.Tests.ps1 - public class ServerPermissionSet + public class ServerPermissionSet { public ServerPermissionSet(){} public ServerPermissionSet( - bool alterAnyAvailabilityGroup, - bool alterAnyEndPoint, - bool connectSql, + bool alterAnyAvailabilityGroup, + bool alterAnyEndpoint, + bool connectSql, bool viewServerState ) { - this.AlterAnyAvailabilityGroup = alterAnyAvailabilityGroup; - this.AlterAnyEndPoint = alterAnyEndPoint; + this.AlterAnyAvailabilityGroup = alterAnyAvailabilityGroup; + this.AlterAnyEndpoint = alterAnyEndpoint; this.ConnectSql = connectSql; this.ViewServerState = viewServerState; - } - + } + public bool AlterAnyAvailabilityGroup = false; - public bool AlterAnyEndPoint = false; + public bool AlterAnyEndpoint = false; public bool ConnectSql = false; public bool ViewServerState = false; } // TypeName: Microsoft.SqlServer.Management.Smo.ServerPermissionInfo // BaseType: Microsoft.SqlServer.Management.Smo.PermissionInfo - // Used by: + // Used by: // xSQLServerPermission.Tests.ps1 - public class ServerPermissionInfo + public class ServerPermissionInfo { public ServerPermissionInfo() { @@ -120,39 +176,39 @@ public ServerPermissionInfo() this.PermissionType = permissionSet; } - public ServerPermissionInfo( + public ServerPermissionInfo( Microsoft.SqlServer.Management.Smo.ServerPermissionSet[] permissionSet ) { this.PermissionType = permissionSet; } - + public Microsoft.SqlServer.Management.Smo.ServerPermissionSet[] PermissionType; public string PermissionState = "Grant"; } // TypeName: Microsoft.SqlServer.Management.Smo.DatabasePermissionSet // BaseType: Microsoft.SqlServer.Management.Smo.PermissionSetBase - // Used by: + // Used by: // xSQLServerDatabasePermission.Tests.ps1 - public class DatabasePermissionSet + public class DatabasePermissionSet { public DatabasePermissionSet(){} public DatabasePermissionSet( bool connect, bool update ) { - this.Connect = connect; + 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: + // Used by: // xSQLServerDatabasePermission.Tests.ps1 - public class DatabasePermissionInfo + public class DatabasePermissionInfo { public DatabasePermissionInfo() { @@ -164,18 +220,18 @@ public DatabasePermissionInfo( Microsoft.SqlServer.Management.Smo.DatabasePermis { 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: + // Used by: // xSQLServerPermission // MSFT_xSQLServerLogin - public class Server - { + public class Server + { public string MockGranteeName; public string Name; @@ -185,32 +241,39 @@ public class Server public bool IsClustered = false; public bool IsHadrEnabled = false; - public Server(){} + public Server(){} + + public Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[] EnumServerPermissions( string principal, Microsoft.SqlServer.Management.Smo.ServerPermissionSet permissionSetQuery ) + { + Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[] permissionInfo = null; + List listOfServerPermissionInfo = null; - public Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[] EnumServerPermissions( string principal, Microsoft.SqlServer.Management.Smo.ServerPermissionSet permissionSetQuery ) - { - List listOfServerPermissionInfo = new List(); - if( Globals.GenerateMockData ) { - Microsoft.SqlServer.Management.Smo.ServerPermissionSet[] permissionSet = { + listOfServerPermissionInfo = new List(); + + Microsoft.SqlServer.Management.Smo.ServerPermissionSet[] permissionSet = { + // AlterAnyEndpoint is set to false to test when permissions are missing. + + // AlterAnyAvailabilityGroup is set to true. new Microsoft.SqlServer.Management.Smo.ServerPermissionSet( true, false, false, false ), - new Microsoft.SqlServer.Management.Smo.ServerPermissionSet( false, true, false, false ), + // ConnectSql is set to true. new Microsoft.SqlServer.Management.Smo.ServerPermissionSet( false, false, true, false ), + // ViewServerState is set to true. new Microsoft.SqlServer.Management.Smo.ServerPermissionSet( false, false, false, true ) }; listOfServerPermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.ServerPermissionInfo( permissionSet ) ); - } else { - listOfServerPermissionInfo.Add( new Microsoft.SqlServer.Management.Smo.ServerPermissionInfo() ); } - Microsoft.SqlServer.Management.Smo.ServerPermissionInfo[] permissionInfo = listOfServerPermissionInfo.ToArray(); + if( listOfServerPermissionInfo != null ) { + permissionInfo = listOfServerPermissionInfo.ToArray(); + } return permissionInfo; } public void Grant( Microsoft.SqlServer.Management.Smo.ServerPermissionSet permission, string granteeName ) { - if( granteeName != this.MockGranteeName ) + if( granteeName != this.MockGranteeName ) { string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; throw new System.ArgumentException(errorMessage, "granteeName"); @@ -219,7 +282,7 @@ public void Grant( Microsoft.SqlServer.Management.Smo.ServerPermissionSet permis public void Revoke( Microsoft.SqlServer.Management.Smo.ServerPermissionSet permission, string granteeName ) { - if( granteeName != this.MockGranteeName ) + if( granteeName != this.MockGranteeName ) { string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; throw new System.ArgumentException(errorMessage, "granteeName"); @@ -229,9 +292,9 @@ public void Revoke( Microsoft.SqlServer.Management.Smo.ServerPermissionSet permi // TypeName: Microsoft.SqlServer.Management.Smo.Login // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase - // Used by: + // Used by: // MSFT_xSQLServerLogin - public class Login + public class Login { private bool _mockPasswordPassed = false; @@ -240,6 +303,7 @@ public class Login public bool MustChangePassword = false; public bool PasswordPolicyEnforced = false; public bool PasswordExpirationEnabled = false; + public bool IsDisabled = false; public string MockName; public LoginType MockLoginType; @@ -248,12 +312,12 @@ 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) ) ) @@ -272,15 +336,15 @@ public void Alter() } } } - + public void ChangePassword( SecureString secureString ) { IntPtr valuePtr = IntPtr.Zero; try { - valuePtr = Marshal.SecureStringToGlobalAllocUnicode(secureString); + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(secureString); if ( Marshal.PtrToStringUni(valuePtr) == "pw" ) - { + { throw new FailedOperationException ( "FailedOperationException", new SmoException ( @@ -308,7 +372,7 @@ public void ChangePassword( SecureString secureString ) Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); } } - + public void Create() { if( this.LoginType == LoginType.Unknown ) { @@ -350,7 +414,7 @@ public void Create( SecureString password, LoginCreateOptions options ) { valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password); if ( Marshal.PtrToStringUni(valuePtr) == "pw" ) - { + { throw new FailedOperationException ( "FailedOperationException", new SmoException ( @@ -373,9 +437,9 @@ public void Create( SecureString password, LoginCreateOptions options ) throw new Exception (); } else - { + { _mockPasswordPassed = true; - + this.Create(); } } @@ -404,28 +468,28 @@ public void Drop() } } } - + // TypeName: Microsoft.SqlServer.Management.Smo.ServerRole // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase - // Used by: + // Used by: // MSFT_xSQLServerRole - public class ServerRole + public class ServerRole { public ServerRole( Server server, string name ) { this.Name = name; - } + } public ServerRole( Object server, string name ) { this.Name = name; - } - + } + public string Name; } // TypeName: Microsoft.SqlServer.Management.Smo.Database // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase - // Used by: + // Used by: // MSFT_xSQLServerDatabase // MSFT_xSQLServerDatabasePermission public class Database @@ -434,28 +498,28 @@ public class Database public Database( Server server, string name ) { this.Name = name; - } + } public Database( Object server, string name ) { this.Name = name; - } - + } + public string Name; - + public void Create() { } - + public void Drop() { } - - public Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] EnumDatabasePermissions( string granteeName ) - { + + public Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] EnumDatabasePermissions( string granteeName ) + { List listOfDatabasePermissionInfo = new List(); - + if( Globals.GenerateMockData ) { - Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { + Microsoft.SqlServer.Management.Smo.DatabasePermissionSet[] permissionSet = { new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( true, false ), new Microsoft.SqlServer.Management.Smo.DatabasePermissionSet( false, true ) }; @@ -472,7 +536,7 @@ public Microsoft.SqlServer.Management.Smo.DatabasePermissionInfo[] EnumDatabaseP public void Grant( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permission, string granteeName ) { - if( granteeName != this.MockGranteeName ) + if( granteeName != this.MockGranteeName ) { string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; throw new System.ArgumentException(errorMessage, "granteeName"); @@ -481,7 +545,7 @@ public void Grant( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet perm public void Deny( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permission, string granteeName ) { - if( granteeName != this.MockGranteeName ) + if( granteeName != this.MockGranteeName ) { string errorMessage = "Expected to get granteeName == '" + this.MockGranteeName + "'. But got '" + granteeName + "'"; throw new System.ArgumentException(errorMessage, "granteeName"); @@ -491,14 +555,14 @@ public void Deny( Microsoft.SqlServer.Management.Smo.DatabasePermissionSet permi // TypeName: Microsoft.SqlServer.Management.Smo.User // BaseType: Microsoft.SqlServer.Management.Smo.ScriptNameObjectBase - // Used by: + // Used by: // xSQLServerDatabaseRole.Tests.ps1 - public class User + public class User { public User( Server server, string name ) { this.Name = name; - } + } public User( Object server, string name ) { @@ -518,7 +582,7 @@ public void Drop() } // TypeName: Microsoft.SqlServer.Management.Smo.SqlServerManagementException - // BaseType: System.Exception + // BaseType: System.Exception // Used by: // xSqlServerLogin.Tests.ps1 public class SqlServerManagementException : Exception @@ -531,7 +595,7 @@ public SqlServerManagementException (string message, Exception inner) : base (me } // TypeName: Microsoft.SqlServer.Management.Smo.SmoException - // BaseType: Microsoft.SqlServer.Management.Smo.SqlServerManagementException + // BaseType: Microsoft.SqlServer.Management.Smo.SqlServerManagementException // Used by: // xSqlServerLogin.Tests.ps1 public class SmoException : SqlServerManagementException @@ -539,7 +603,7 @@ public class SmoException : SqlServerManagementException public SmoException () : base () {} public SmoException (string message) : base (message) {} - + public SmoException (string message, SqlServerManagementException inner) : base (message, inner) {} } @@ -550,9 +614,9 @@ public SmoException (string message, SqlServerManagementException inner) : base public class FailedOperationException : SmoException { public FailedOperationException () : base () {} - + public FailedOperationException (string message) : base (message) {} - + public FailedOperationException (string message, SmoException inner) : base (message, inner) {} } @@ -585,7 +649,7 @@ public void Alter() } } } - + // TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityReplica // BaseType: Microsoft.SqlServer.Management.Smo.NamedSmoObject // Used by: @@ -605,7 +669,9 @@ public AvailabilityReplica( AvailabilityGroup availabilityGroup, string name ) public string EndpointUrl; public string FailoverMode; public string Name; - + public string ReadOnlyRoutingConnectionUrl; + public string[] ReadOnlyRoutingList; + public void Alter() { if ( this.Name == "AlterFailed" ) diff --git a/Tests/Unit/Stubs/SQLPSStub.psm1 b/Tests/Unit/Stubs/SQLPSStub.psm1 index 0b14d91a9..543ed0d39 100644 --- a/Tests/Unit/Stubs/SQLPSStub.psm1 +++ b/Tests/Unit/Stubs/SQLPSStub.psm1 @@ -948,6 +948,9 @@ function Remove-SqlAvailabilityReplica { [object] ${InputObject}, + [switch] + ${Confirm}, + [switch] ${Script} ) diff --git a/Tests/Unit/xSQLServerHelper.Tests.ps1 b/Tests/Unit/xSQLServerHelper.Tests.ps1 index 6ffc81822..083076e11 100644 --- a/Tests/Unit/xSQLServerHelper.Tests.ps1 +++ b/Tests/Unit/xSQLServerHelper.Tests.ps1 @@ -1,3 +1,7 @@ +# To run these tests, we have to fake login credentials +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param () + # Unit Test Template Version: 1.1.0 $script:moduleName = 'xSQLServerHelper' @@ -17,10 +21,42 @@ Add-Type -Path ( Join-Path -Path ( Join-Path -Path $PSScriptRoot -ChildPath Stub # Begin Testing InModuleScope $script:moduleName { - Describe 'Testing Restart-SqlService' { + $mockNewObject_MicrosoftAnalysisServicesServer = { + return New-Object Object | + Add-Member ScriptMethod Connect { + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $dataSource + ) + + if ($dataSource -ne $mockExpectedDataSource) + { + throw ("Datasource was expected to be '{0}', but was '{1}'." -f $mockExpectedDataSource,$dataSource) + } - Context 'Restart-SqlService standalone instance' { + if ($mockThrowInvalidOperation) + { + throw 'Unable to connect.' + } + } -PassThru -Force + } + + $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter = { + $TypeName -eq 'Microsoft.AnalysisServices.Server' + } + $mockSqlMajorVersion = 13 + $mockInstanceName = 'TEST' + + $mockSetupCredentialUserName = 'TestUserName12345' + $mockSetupCredentialPassword = 'StrongOne7.' + $mockSetupCredentialSecurePassword = ConvertTo-SecureString -String $mockSetupCredentialPassword -AsPlainText -Force + $mockSetupCredential = New-Object PSCredential ($mockSetupCredentialUserName, $mockSetupCredentialSecurePassword) + + Describe 'Testing Restart-SqlService' { + Context 'Restart-SqlService standalone instance' { Mock -CommandName Connect-SQL -MockWith { return @{ Name = 'MSSQLSERVER' @@ -51,7 +87,7 @@ InModuleScope $script:moduleName { Name = 'MSSQLSERVER' DisplayName = 'Microsoft SQL Server (MSSQLSERVER)' DependentServices = @( - @{ + @{ Name = 'SQLSERVERAGENT' DisplayName = 'SQL Server Agent (MSSQLSERVER)' Status = 'Running' @@ -76,7 +112,7 @@ InModuleScope $script:moduleName { Name = 'MSSQL$STOPPEDAGENT' DisplayName = 'Microsoft SQL Server (STOPPEDAGENT)' DependentServices = @( - @{ + @{ Name = 'SQLAGENT$STOPPEDAGENT' DisplayName = 'SQL Server Agent (STOPPEDAGENT)' Status = 'Stopped' @@ -119,7 +155,6 @@ InModuleScope $script:moduleName { } Context 'Restart-SqlService clustered instance' { - Mock -CommandName Connect-SQL -MockWith { return @{ Name = 'MSSQLSERVER' @@ -150,7 +185,7 @@ InModuleScope $script:moduleName { Mock -CommandName Get-CimInstance -MockWith { @('MSSQLSERVER','NAMEDINSTANCE','STOPPEDAGENT') | ForEach-Object { $mock = New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource','root/MSCluster' - + $mock | Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server ($($_))" -TypeName 'String' $mock | Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server' -TypeName 'String' $mock | Add-Member -MemberType NoteProperty -Name 'PrivateProperties' -Value @{ InstanceName = $_ } @@ -161,26 +196,26 @@ InModuleScope $script:moduleName { Mock -CommandName Get-CimAssociatedInstance -MockWith { $mock = New-Object Microsoft.Management.Infrastructure.CimInstance 'MSCluster_Resource','root/MSCluster' - + $mock | Add-Member -MemberType NoteProperty -Name 'Name' -Value "SQL Server Agent ($($InputObject.PrivateProperties.InstanceName))" -TypeName 'String' $mock | Add-Member -MemberType NoteProperty -Name 'Type' -Value 'SQL Server Agent' -TypeName 'String' $mock | Add-Member -MemberType NoteProperty -Name 'State' -Value (@{ $true = 3; $false = 2 }[($InputObject.PrivateProperties.InstanceName -eq 'STOPPEDAGENT')]) -TypeName 'Int32' - + return $mock } -Verifiable -ParameterFilter { $ResultClassName -eq 'MSCluster_Resource' } Mock -CommandName Invoke-CimMethod -MockWith {} -Verifiable -ParameterFilter { $MethodName -eq 'TakeOffline' } - Mock -CommandName Invoke-CimMethod -MockWith {} -Verifiable -ParameterFilter { $MethodName -eq 'BringOnline' } + Mock -CommandName Invoke-CimMethod -MockWith {} -Verifiable -ParameterFilter { $MethodName -eq 'BringOnline' } It 'Should restart SQL Server and SQL Agent resources for a clustered default instance' { { Restart-SqlService -SQLServer 'CLU01' } | Should Not Throw - + Assert-MockCalled -CommandName Connect-SQL -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 } It 'Should restart SQL Server and SQL Agent resources for a clustered named instance' { @@ -190,7 +225,7 @@ InModuleScope $script:moduleName { Assert-MockCalled -CommandName Get-CimInstance -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Get-CimAssociatedInstance -Scope It -Exactly -Times 1 Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'TakeOffline' } -Scope It -Exactly -Times 1 - Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 + Assert-MockCalled -CommandName Invoke-CimMethod -ParameterFilter { $MethodName -eq 'BringOnline' } -Scope It -Exactly -Times 2 } It 'Should not try to restart a SQL Agent resource that is not online' { @@ -205,327 +240,97 @@ InModuleScope $script:moduleName { } } - Describe "Testing Get-SqlDatabasePermission" { - $mockSqlServerObject = [pscustomobject]@{ - InstanceName = 'MSSQLSERVER' - ComputerNamePhysicalNetBIOS = 'SQL01' + Describe 'Testing Connect-SQLAnalysis' { + BeforeEach { + Mock -CommandName New-Object ` + -MockWith $mockNewObject_MicrosoftAnalysisServicesServer ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter ` + -Verifiable } - $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 + Context 'When connecting to the default instance using Windows Authentication' { + It 'Should not throw when connecting' { + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME" - $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 - + { Connect-SQLAnalysis } | Should -Not -Throw - 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'." + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } } - 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 connecting to the named instance using Windows Authentication' { + It 'Should not throw when connecting' { + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME\$mockInstanceName" - 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' - } + { Connect-SQLAnalysis -SQLInstanceName $mockInstanceName } | Should -Not -Throw - It 'Should not return any permissions' { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $false - - $permission = Get-SqlDatabasePermission @testParameters - $permission | Should BeNullOrEmpty + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } - } - 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' - } + Context 'When connecting to the named instance using Windows Authentication impersonation' { + It 'Should not throw when connecting' { + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME\$mockInstanceName;User ID=$mockSetupCredentialUserName;Password=$mockSetupCredentialPassword" - It 'Should return the correct permissions' { - [Microsoft.SqlServer.Management.Smo.Globals]::GenerateMockData = $true + { Connect-SQLAnalysis -SQLInstanceName $mockInstanceName -SetupCredential $mockSetupCredential } | Should -Not -Throw - $permission = Get-SqlDatabasePermission @testParameters - $permission -contains 'Connect' | Should Be $true - $permission -contains 'Update' | Should Be $true + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } } - 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' - } - + Context 'When connecting to the default instance using the correct service instance but does not return a correct Analysis Service object' { It 'Should throw the correct error' { - { Get-SqlDatabaseRecoveryModel @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." - } - } + $mockExpectedDataSource = '' - Context 'When the specified database exist' { - $testParameters = @{ - SqlServerObject = $mockSqlServerObject - DatabaseName = 'AdventureWorks' - } + Mock -CommandName New-Object ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter ` + -Verifiable - It 'Should return the current RecoveryModel' { - $recoveryModel = Get-SqlDatabaseRecoveryModel @testParameters - $recoveryModel | Should Be $testParameters.SqlServerObject.Databases.AdventureWorks.RecoveryModel - } - } + $mockCorrectErrorMessage = ('Failed to connect to Analysis Services ''{0}''. InnerException: Did not get the expected Analysis Services server object.' -f $env:COMPUTERNAME) + { Connect-SQLAnalysis } | Should -Throw $mockCorrectErrorMessage - 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 + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } } - Context 'When the specified database does not exist' { - $testParameters = @{ - SqlServerObject = $mockSqlServerObject - DatabaseName = 'UnknownDatabase' - RecoveryModel = 'Simple' - } - + Context 'When connecting to the default instance using a Analysis Service instance that does not exist' { It 'Should throw the correct error' { - { Set-SqlDatabaseRecoveryModel @testParameters } | Should Throw "Database 'UnknownDatabase' does not exist on SQL server 'SQL01\MSSQLSERVER'." - } - } + $mockExpectedDataSource = "Data Source=$env:COMPUTERNAME" - Context 'When the specified database and the system is not in desired state' { - $testParameters = @{ - SqlServerObject = $mockSqlServerObject - DatabaseName = 'AdventureWorks' - RecoveryModel = 'Simple' - } + # Force the mock of Connect() method to throw 'Unable to connect.' + $mockThrowInvalidOperation = $true - It 'Should set the correct RecoveryModel without throwing an error' { - $mockExpectedRecoveryModelForAlterMethod = $testParameters.RecoveryModel - { Set-SqlDatabaseRecoveryModel @testParameters } | Should Not Throw - } - } + $mockCorrectErrorMessage = ('Failed to connect to Analysis Services ''{0}''. InnerException: Exception calling "Connect" with "1" argument(s): "Unable to connect."' -f $env:COMPUTERNAME) + { Connect-SQLAnalysis } | Should -Throw $mockCorrectErrorMessage - Assert-VerifiableMocks - } + Assert-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter - 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'." + # Setting it back to the default so it does not disturb other tests. + $mockThrowInvalidOperation = $false } } - Context 'When the specified login does not exist' { - $testParameters = @{ - Sql = $mockSqlServerObject - Name = 'CONTOSO\UnknownUser' - Database = 'AdventureWorks' - PermissionState = 'Grant' - Permissions = @( 'Connect','Update' ) - } - + # This test is to test the mock so that it throws correct when data source is not the expected data source + Context 'When connecting to the named instance using another data source then expected' { It 'Should throw the correct error' { - { Add-SqlDatabasePermission @testParameters } | Should Throw "Login 'CONTOSO\UnknownUser' does not exist on SQL server 'SQL01\MSSQLSERVER'." - } - } - - Assert-VerifiableMocks - } + $mockExpectedDataSource = "Force wrong datasource" - 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'." - } - } + $mockCorrectErrorMessage = 'Failed to connect to Analysis Services ''DummyHost\TEST''. InnerException: Exception calling "Connect" with "1" argument(s): "Datasource was expected to be ''Force wrong datasource'', but was ''Data Source=DummyHost\TEST''."' + { Connect-SQLAnalysis -SQLServer 'DummyHost' -SQLInstanceName $mockInstanceName } | Should -Throw $mockCorrectErrorMessage - 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-MockCalled -CommandName New-Object -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockNewObject_MicrosoftAnalysisServicesServer_ParameterFilter } } Assert-VerifiableMocks } - + Describe 'Testing Invoke-Query' { $mockExpectedQuery = '' @@ -570,7 +375,7 @@ InModuleScope $script:moduleName { ) ) } - + BeforeEach { Mock -CommandName Connect-SQL -MockWith $mockConnectSql -ModuleName $script:DSCResourceName -Verifiable } @@ -582,12 +387,12 @@ InModuleScope $script:moduleName { Database = 'master' Query = '' } - + Context 'Execute a query with no results' { It 'Should execute the query silently' { $queryParams.Query = "EXEC sp_configure 'show advanced option', '1'" $mockExpectedQuery = $queryParams.Query.Clone() - + { Invoke-Query @queryParams } | Should Not Throw Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly @@ -596,7 +401,7 @@ InModuleScope $script:moduleName { It 'Should throw the correct error, ExecuteNonQueryFailed, when executing the query fails' { $queryParams.Query = 'BadQuery' - + { Invoke-Query @queryParams } | Should Throw 'ExecuteNonQueryFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly @@ -608,7 +413,7 @@ InModuleScope $script:moduleName { It 'Should execute the query and return a result set' { $queryParams.Query = 'SELECT name FROM sys.databases' $mockExpectedQuery = $queryParams.Query.Clone() - + Invoke-Query @queryParams -WithResults | Should Not BeNullOrEmpty Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly @@ -617,7 +422,7 @@ InModuleScope $script:moduleName { It 'Should throw the correct error, ExecuteQueryWithResultsFailed, when executing the query fails' { $queryParams.Query = 'BadQuery' - + { Invoke-Query @queryParams -WithResults } | Should Throw 'ExecuteQueryWithResultsFailed' Assert-MockCalled -CommandName Connect-SQL -Scope It -Times 1 -Exactly @@ -625,4 +430,394 @@ InModuleScope $script:moduleName { } } } + + Describe "Testing Update-AvailabilityGroupReplica" { + Mock -CommandName New-TerminatingError { $ErrorType } -Verifiable + + Context 'When the Availability Group Replica is altered' { + It 'Should silently alter the Availability Group Replica' { + $availabilityReplica = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + + { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should Not Throw + + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 0 -Exactly + } + + It 'Should throw the correct error, AlterAvailabilityGroupReplicaFailed, when altering the Availaiblity Group Replica fails' { + $availabilityReplica = New-Object Microsoft.SqlServer.Management.Smo.AvailabilityReplica + $availabilityReplica.Name = 'AlterFailed' + + { Update-AvailabilityGroupReplica -AvailabilityGroupReplica $availabilityReplica } | Should Throw 'AlterAvailabilityGroupReplicaFailed' + + Assert-MockCalled -CommandName New-TerminatingError -Scope It -Times 1 -Exactly + } + } + } + + Describe "Testing Test-LoginEffectivePermissions" { + + $mockAllPermissionsPresent = @( + 'Connect SQL', + 'Alter Any Availability Group', + 'View Server State' + ) + + $mockPermissionsMissing = @( + 'Connect SQL', + 'View Server State' + ) + + $mockInvokeQueryClusterServicePermissionsSet = @() # Will be set dynamically in the check + + $mockInvokeQueryClusterServicePermissionsResult = { + return New-Object PSObject -Property @{ + Tables = @{ + Rows = @{ + permission_name = $mockInvokeQueryClusterServicePermissionsSet + } + } + } + } + + $testLoginEffectivePermissionsParams = @{ + SQLServer = 'Server1' + SQLInstanceName = 'MSSQLSERVER' + Login = 'NT SERVICE\ClusSvc' + Permissions = @() + } + + BeforeEach { + Mock -CommandName Invoke-Query -MockWith $mockInvokeQueryClusterServicePermissionsResult -Verifiable + } + + Context 'When all of the permissions are present' { + + It 'Should return $true when the desired permissions are present' { + $mockInvokeQueryClusterServicePermissionsSet = $mockAllPermissionsPresent.Clone() + $testLoginEffectivePermissionsParams.Permissions = $mockAllPermissionsPresent.Clone() + + Test-LoginEffectivePermissions @testLoginEffectivePermissionsParams | Should Be $true + + Assert-MockCalled -CommandName Invoke-Query -Times 1 -Exactly + } + } + + Context 'When a permission is missing' { + + It 'Should return $false when the desired permissions are not present' { + $mockInvokeQueryClusterServicePermissionsSet = $mockPermissionsMissing.Clone() + $testLoginEffectivePermissionsParams.Permissions = $mockAllPermissionsPresent.Clone() + + Test-LoginEffectivePermissions @testLoginEffectivePermissionsParams | Should Be $false + + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + } + + It 'Should return $false when the specified login has no permissions assigned' { + $mockInvokeQueryClusterServicePermissionsSet = @() + $testLoginEffectivePermissionsParams.Permissions = $mockAllPermissionsPresent.Clone() + + Test-LoginEffectivePermissions @testLoginEffectivePermissionsParams | Should Be $false + + Assert-MockCalled -CommandName Invoke-Query -Scope It -Times 1 -Exactly + } + } + } + + $mockImportModule = { + if ($Name -ne $mockExpectedModuleNameToImport) + { + throw ('Wrong module was loaded. Expected {0}, but was {1}.' -f $mockExpectedModuleNameToImport, $Name[0]) + } + } + + $mockGetModule = { + return New-Object PSObject -Property @{ + Name = $mockModuleNameToImport + } + } + + $mockGetModule_SqlServer_ParameterFilter = { + $FullyQualifiedName.Name -eq 'SqlServer' -and $ListAvailable -eq $true + } + + $mockGetModule_SQLPS_ParameterFilter = { + $FullyQualifiedName.Name -eq 'SQLPS' -and $ListAvailable -eq $true + } + + Describe 'Testing Import-SQLPSModule' -Tag ImportSQLPSModule { + BeforeEach { + Mock -CommandName Push-Location -Verifiable + Mock -CommandName Pop-Location -Verifiable + Mock -CommandName Import-Module -MockWith $mockImportModule -Verifiable + } + + Context 'When module SqlServer exists' { + $mockModuleNameToImport = 'SqlServer' + $mockExpectedModuleNameToImport = 'SqlServer' + + It 'Should import the SqlServer module without throwing' { + Mock -CommandName Get-Module -MockWith $mockGetModule -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + + { Import-SQLPSModule } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + } + } + + Context 'When only module SQLPS exists' { + $mockModuleNameToImport = 'SQLPS' + $mockExpectedModuleNameToImport = 'SQLPS' + + It 'Should import the SqlServer module without throwing' { + Mock -CommandName Get-Module -MockWith $mockGetModule -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Verifiable + Mock -CommandName Get-Module -MockWith { + return $null + } -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + + { Import-SQLPSModule } | Should -Not -Throw + + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-Module -ParameterFilter $mockGetModule_SQLPS_ParameterFilter -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + } + } + + Context 'When neither SqlServer or SQLPS exists' { + $mockModuleNameToImport = 'UnknownModule' + $mockExpectedModuleNameToImport = 'SQLPS' + + It 'Should throw the correct error message' { + Mock -CommandName Get-Module + + { Import-SQLPSModule } | Should -Throw 'Neither SqlServer module or SQLPS module was found.' + + Assert-MockCalled -CommandName Get-Module -Exactly -Times 2 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It + } + } + + Context 'When Import-Module fails to load the module' { + $mockModuleNameToImport = 'SqlServer' + $mockExpectedModuleNameToImport = 'SqlServer' + + It 'Should throw the correct error message' { + Mock -CommandName Get-Module -MockWith $mockGetModule -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + Mock -CommandName Import-Module -MockWith { + throw 'Mock Import-Module throwing a mocked error.' + } + + { Import-SQLPSModule } | Should -Throw 'Failed to import SqlServer module. InnerException: Mock Import-Module throwing a mocked error.' + + Assert-MockCalled -CommandName Get-Module -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + } + } + + # This is to test the tests (so the mock throws correctly) + Context 'When mock Import-Module is called with wrong module name' { + $mockModuleNameToImport = 'SqlServer' + $mockExpectedModuleNameToImport = 'UnknownModule' + + It 'Should throw the correct error message' { + Mock -CommandName Get-Module -MockWith $mockGetModule -ParameterFilter $mockGetModule_SqlServer_ParameterFilter -Verifiable + + { Import-SQLPSModule } | Should -Throw 'Failed to import SqlServer module. InnerException: Wrong module was loaded. Expected UnknownModule, but was SqlServer.' + + Assert-MockCalled -CommandName Get-Module -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Push-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Pop-Location -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMocks + } + + $mockGetItemProperty_MicrosoftSQLServer_InstanceNames_SQL = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name $mockInstanceName -Value $mockInstance_InstanceId -PassThru -Force + ) + ) + } + + $mockGetItemProperty_MicrosoftSQLServer_FullInstanceId_Setup = { + return @( + ( + New-Object Object | + Add-Member -MemberType NoteProperty -Name 'Version' -Value "$($mockSqlMajorVersion).0.4001.0" -PassThru -Force + ) + ) + } + + $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL = { + $Path -eq 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' + } + + $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup = { + $Path -eq "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$mockInstance_InstanceId\Setup" + } + + Describe 'Testing Get-SqlInstanceMajorVersion' -Tag GetSqlInstanceMajorVersion { + BeforeEach { + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL ` + -MockWith $mockGetItemProperty_MicrosoftSQLServer_InstanceNames_SQL ` + -Verifiable + + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup ` + -MockWith $mockGetItemProperty_MicrosoftSQLServer_FullInstanceId_Setup ` + -Verifiable + } + + $mockInstance_InstanceId = "MSSQL$($mockSqlMajorVersion).$($mockInstanceName)" + + Context 'When calling Get-SqlInstanceMajorVersion' { + It 'Should return the correct major SQL version number' { + $result = Get-SqlInstanceMajorVersion -SQLInstanceName $mockInstanceName + $result | Should -Be $mockSqlMajorVersion + + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL + + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup + } + } + + Context 'When calling Get-SqlInstanceMajorVersion and nothing is returned' { + It 'Should throw the correct error' { + Mock -CommandName Get-ItemProperty ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup ` + -MockWith { + return New-Object Object + } -Verifiable + + { Get-SqlInstanceMajorVersion -SQLInstanceName $mockInstanceName } | Should -Throw 'Could not get the SQL version for the instance ''TEST''.' + + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_InstanceNames_SQL + + Assert-MockCalled -CommandName Get-ItemProperty -Exactly -Times 1 -Scope It ` + -ParameterFilter $mockGetItemProperty_ParameterFilter_MicrosoftSQLServer_FullInstanceId_Setup + } + } + + Assert-VerifiableMocks + } + + $mockApplicationDomainName = 'xSQLServerHelperTests' + $mockApplicationDomainObject = [System.AppDomain]::CreateDomain($mockApplicationDomainName) + + <# + It is not possible to fully test this helper function since we can't mock having the correct assembly + in the GAC. So these test will try to load the wrong assembly and will catch the error. But that means + it will never test the rows after if fails to load the assembly. + #> + Describe 'Testing Register-SqlSmo' -Tag RegisterSqlSmo { + BeforeEach { + Mock -CommandName Get-SqlInstanceMajorVersion -MockWith { + return '0' # Mocking zero because that could never match a correct assembly + } -Verifiable + } + + Context 'When calling Register-SqlSmo to load the wrong assembly' { + It 'Should throw with the correct error' { + { + Register-SqlSmo -SQLInstanceName $mockInstanceName + } | Should -Throw 'Exception calling "Load" with "1" argument(s): "Could not load file or assembly ''Microsoft.SqlServer.Smo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'' or one of its dependencies. The system cannot find the file specified."' + + Assert-MockCalled -CommandName Get-SqlInstanceMajorVersion + } + } + + Context 'When calling Register-SqlSmo with a application domain to load the wrong assembly' { + It 'Should throw with the correct error' { + { + Register-SqlSmo -SQLInstanceName $mockInstanceName -ApplicationDomain $mockApplicationDomainObject + } | Should -Throw 'Exception calling "Load" with "1" argument(s): "Could not load file or assembly ''Microsoft.SqlServer.Smo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'' or one of its dependencies. The system cannot find the file specified."' + + Assert-MockCalled -CommandName Get-SqlInstanceMajorVersion + } + } + + Assert-VerifiableMocks + } + + <# + It is not possible to fully test this helper function since we can't mock having the correct assembly + in the GAC. So these test will try to load the wrong assembly and will catch the error. But that means + it will never test the rows after if fails to load the assembly. + #> + Describe 'Testing Register-SqlWmiManagement' -Tag RegisterSqlWmiManagement { + BeforeEach { + Mock -CommandName Get-SqlInstanceMajorVersion -MockWith { + return '0' # Mocking zero because that could never match a correct assembly + } -Verifiable + + Mock -CommandName Register-SqlSmo -MockWith { + [System.AppDomain]::CreateDomain('xSQLServerHelper') + } -ParameterFilter { + $SQLInstanceName -eq $mockInstanceName + } -Verifiable + } + + Context 'When calling Register-SqlWmiManagement to load the wrong assembly' { + It 'Should throw with the correct error' { + { + Register-SqlWmiManagement -SQLInstanceName $mockInstanceName + } | Should -Throw 'Exception calling "Load" with "1" argument(s): "Could not load file or assembly ''Microsoft.SqlServer.SqlWmiManagement, Version=0.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'' or one of its dependencies. The system cannot find the file specified."' + + Assert-MockCalled -CommandName Get-SqlInstanceMajorVersion + Assert-MockCalled -CommandName Register-SqlSmo -Exactly -Times 1 -Scope It -ParameterFilter { + $SQLInstanceName -eq $mockInstanceName -and $ApplicationDomain -eq $null + } + } + } + + Context 'When calling Register-SqlWmiManagement with a application domain to load the wrong assembly' { + It 'Should throw with the correct error' { + { + Register-SqlWmiManagement -SQLInstanceName $mockInstanceName -ApplicationDomain $mockApplicationDomainObject + } | Should -Throw 'Exception calling "Load" with "1" argument(s): "Could not load file or assembly ''Microsoft.SqlServer.SqlWmiManagement, Version=0.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'' or one of its dependencies. The system cannot find the file specified."' + + Assert-MockCalled -CommandName Get-SqlInstanceMajorVersion + Assert-MockCalled -CommandName Register-SqlSmo -Exactly -Times 0 -Scope It -ParameterFilter { + $SQLInstanceName -eq $mockInstanceName -and $ApplicationDomain -eq $null + } + + Assert-MockCalled -CommandName Register-SqlSmo -Exactly -Times 1 -Scope It -ParameterFilter { + $SQLInstanceName -eq $mockInstanceName -and $ApplicationDomain.FriendlyName -eq $mockApplicationDomainName + } + } + } + + Assert-VerifiableMocks + } + + <# + NOTE! This test must be after the tests for Register-SqlSmo and Register-SqlWmiManagement. + This test unloads the application domain that is used during those tests. + #> + Describe 'Testing Unregister-SqlAssemblies' -Tag UnregisterSqlAssemblies { + Context 'When calling Unregister-SqlAssemblies to unload the assemblies' { + It 'Should not throw an error' { + { + Unregister-SqlAssemblies -ApplicationDomain $mockApplicationDomainObject + } | Should -Not -Throw + } + } + } } diff --git a/en-US/xSQLServer.strings.psd1 b/en-US/xSQLServer.strings.psd1 index 30b3d3698..4613070d9 100644 --- a/en-US/xSQLServer.strings.psd1 +++ b/en-US/xSQLServer.strings.psd1 @@ -1,14 +1,19 @@ ConvertFrom-StringData @' -###PSLOC +###PSLOC # Common NoKeyFound = No Localization key found for ErrorType: '{0}'. AbsentNotImplemented = Ensure = Absent is not implemented! TestFailedAfterSet = Test-TargetResource returned false after calling set. RemoteConnectionFailed = Remote PowerShell connection to Server '{0}' failed. -TODO = ToDo. Work not implemented at this time. +TODO = ToDo. Work not implemented at this time. UnexpectedErrorFromGet = Got unexpected result from Get-TargetResource. No change is made. -FailedToImportSQLPSModule = Failed to import SQLPS module. +FailedToImportSqlModule = Failed to import {0} module. +SqlModuleNotFound = Neither SqlServer module or SQLPS module was found. NotConnectedToInstance = Was unable to connect to the instance '{0}\\{1}' +AlterAvailabilityGroupFailed = Failed to alter the availability group '{0}'. +HadrNotEnabled = HADR is not enabled. +AvailabilityGroupNotFound = Unable to locate the availability group '{0}' on the instance '{1}'. +SqlServerVersionIsInvalid = 'Could not get the SQL version for the instance '{0}'. # SQLServer NoDatabase = Database '{0}' does not exist on SQL server '{1}\\{2}'. @@ -18,6 +23,11 @@ LoginNotFound = Login '{0}' does not exist on SQL server '{1}\\{2}'." FailedLogin = Creating a login of type 'SqlLogin' requires LoginCredential FeatureNotSupported = '{0}' is not a valid value for setting 'FEATURES'. Refer to SQL Help for more information. +# Database Role +AddLoginDatabaseSetError = Failed adding the login {2} as a user of the database {3}, on the instance {0}\\{1}. +DropMemberDatabaseSetError = Failed removing the login {2} from the role {3} on the database {4}, on the instance {0}\\{1}. +AddMemberDatabaseSetError = Failed adding the login {2} to the role {3} on the database {4}, on the instance {0}\\{1}. + # AvailabilityGroupListener AvailabilityGroupListenerNotFound = Trying to make a change to a listener that does not exist. AvailabilityGroupListenerErrorVerifyExist = Unexpected result when trying to verify existence of listener '{0}'. @@ -27,11 +37,11 @@ AvailabilityGroupListenerDHCPChangeError = IP-address configuration mismatch. Ex # Endpoint EndpointNotFound = Endpoint '{0}' does not exist EndpointErrorVerifyExist = Unexpected result when trying to verify existence of endpoint '{0}'. +EndpointFoundButWrongType = Endpoint '{0}' does exist, but it is not of type 'DatabaseMirroring'. # Permission PermissionGetError = Unexpected result when trying to get permissions for '{0}'. -PrincipalNotFound = Principal '{0}' does not exist. -PermissionMissingEnsure = Ensure is not set. No change can be made. +ChangingPermissionFailed = Changing permission for principal '{0}' failed. # Configuration ConfigurationOptionNotFound = Specified option '{0}' could not be found. @@ -39,6 +49,7 @@ ConfigurationRestartRequired = Configuration option '{0}' has been updated, but # AlwaysOnService AlterAlwaysOnServiceFailed = Failed to ensure Always On is {0} on the instance '{1}'. +UnexpectedAlwaysOnStatus = The status of property Server.IsHadrEnabled was netiher $true or $false. Status is '{0}'. # Login PasswordValidationFailed = Creation of the login '{0}' failed due to the following error: {1} @@ -59,19 +70,24 @@ FailoverClusterIPAddressNotValid = Unable to map the specified IP Address(es) to FailoverClusterResourceNotFound = Could not locate a SQL Server cluster resource for instance {0}. # AlwaysOnAvailabilityGroup -AlterAvailabilityGroupFailed = Failed to alter the availability group '{0}'. AlterAvailabilityGroupReplicaFailed = Failed to alter the avilability group replica '{0}'. ClusterPermissionsMissing = The cluster does not have permissions to manage the Availability Group on '{0}\\{1}'. Grant 'Connect SQL', 'Alter Any Availability Group', and 'View Server State' to either 'NT SERVICE\\ClusSvc' or 'NT AUTHORITY\\SYSTEM'. -CreateAvailabilityGroupReplicaFailed = Creating the Availability Group Replica failed. -CreateAvailabilityGroupFailed = Creating the availability group '{0}' failed with the error '{1}'. +CreateAvailabilityGroupReplicaFailed = Creating the Availability Group Replica '{0}' failed on the instance '{1}'. +CreateAvailabilityGroupFailed = Creating the availability group '{0}'. DatabaseMirroringEndpointNotFound = No database mirroring endpoint was found on '{0}\{1}'. -HadrNotEnabled = HADR is not enabled. InstanceNotPrimaryReplica = The instance '{0}' is not the primary replica for the availability group '{1}'. RemoveAvailabilityGroupFailed = Failed to remove the availabilty group '{0}' from the '{1}' instance. +# AlwaysOnAvailabilityGroupReplica +JoinAvailabilityGroupFailed = Failed to join the availability group replica '{0}'. +RemoveAvailabilityGroupReplicaFailed = Failed to remove the availability group replica '{0}'. +ReplicaNotFound = Unable to find the availability group replica '{0}' on the instance '{1}'. + # SQLServerHelper ExecuteQueryWithResultsFailed = Executing query with results failed on database '{0}'. ExecuteNonQueryFailed = Executing non-query failed on database '{0}'. +AnalysisServicesFailedToConnect = Failed to connect to Analysis Services '{0}'. +AnalysisServicesNoServerObject = Did not get the expected Analysis Services server object. # Max degree of parallelism MaxDopSetError = Unexpected result when trying to configure the max degree of parallelism server configuration option. @@ -88,4 +104,17 @@ CreateDatabaseSetError = Failed to create the database named {2} on {0}\\{1}. DropDatabaseSetError = Failed to drop the database named {2} on {0}\\{1}. FailedToGetOwnerDatabase = Failed to get owner of the database named {0} on {1}\\{2}. FailedToSetOwnerDatabase = Failed to set owner named {0} of the database named {1} on {2}\\{3}. +FailedToSetPermissionDatabase = Failed to set permission for login named {0} of the database named {1} on {2}\\{3}. +FailedToEnumDatabasePermissions = Failed to get permission for login named {0} of the database named {1} on {2}\\{3}. + +# SQLServerRole +EnumMemberNamesServerRoleGetError = Failed to enumerate members of the server role named {2} on {0}\\{1}. +MembersToIncludeAndExcludeParamMustBeNull = The parameter MembersToInclude and/or MembersToExclude must not be set, or be set to $null, when parameter Members are used. +CreateServerRoleSetError = Failed to create the server role named {2} on {0}\\{1}. +DropServerRoleSetError = Failed to drop the server role named {2} on {0}\\{1}. +AddMemberServerRoleSetError = Failed to add member {3} to the server role named {2} on {0}\\{1}. +DropMemberServerRoleSetError = Failed to drop member {3} to the server role named {2} on {0}\\{1}. + +# SQLServerNetwork +UnableToUseBothDynamicAndStaticPort = Unable to set both TCP dynamic port and TCP static port. Only one can be set. '@ diff --git a/xSQLServer.psd1 b/xSQLServer.psd1 index 0020e8a4e..3a615a259 100644 --- a/xSQLServer.psd1 +++ b/xSQLServer.psd1 @@ -1,6 +1,6 @@ @{ # Version number of this module. -ModuleVersion = '5.0.0.0' +ModuleVersion = '7.1.0.0' # ID used to uniquely identify this module GUID = '74e9ddb5-4cbc-4fa2-a222-2bcfb533fd66' @@ -47,111 +47,76 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - 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 - - 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 + ReleaseNotes = '- Changes to xSQLServerMemory + - Changed the way SQLServer parameter is passed from Test-TargetResource to Get-TargetResource so that the default value isn"t lost (issue 576). + - Added condition to unit tests for when no SQLServer parameter is set. +- Changes to xSQLServerMaxDop + - Changed the way SQLServer parameter is passed from Test-TargetResource to Get-TargetResource so that the default value isn"t lost (issue 576). + - Added condition to unit tests for when no SQLServer parameter is set. +- Changes to xWaitForAvailabilityGroup + - Updated README.md with a description for the resources and revised the parameter descriptions. + - The default value for RetryIntervalSec is now 20 seconds and the default value for RetryCount is now 30 times (issue 505). + - Cleaned up code and fixed PSSA rules warnings (issue 268). + - Added unit tests (issue 297). + - Added descriptive text to README.md that the account that runs the resource must have permission to run the cmdlet Get-ClusterGroup (issue 307). + - Added read-only parameter GroupExist which will return $true if the cluster role/group exist, otherwise it returns $false (issue 510). + - Added examples. +- Changes to xSQLServerPermission + - Cleaned up code, removed SupportsShouldProcess and fixed PSSA rules warnings (issue 241 and issue 262). + - It is now possible to add permissions to two or more logins on the same instance (issue 526). + - The parameter NodeName is no longer mandatory and has now the default value of $env:COMPUTERNAME. + - The parameter Ensure now has a default value of "Present". + - Updated README.md with a description for the resources and revised the parameter descriptions. + - Removed dependency of SQLPS provider (issue 482). + - Added ConnectSql permission. Now that permission can also be granted or revoked. + - Updated note in resource description to also mention ConnectSql permission. +- Changes to xSQLServerHelper module + - Removed helper function Get-SQLPSInstance and Get-SQLPSInstanceName because there is no resource using it any longer. + - Added four new helper functions. + - Register-SqlSmo, Register-SqlWmiManagement and Unregister-SqlAssemblies to handle the creation on the application domain and loading and unloading of the SMO and SqlWmiManagement assemblies. + - Get-SqlInstanceMajorVersion to get the major SQL version for a specific instance. + - Fixed typos in comment-based help +- Changes to xSQLServer + - Fixed typos in markdown files; CHANGELOG, CONTRIBUTING, README and ISSUE_TEMPLATE. + - Fixed typos in schema.mof files (and README.md). + - Updated some parameter description in schema.mof files on those that was found was not equal to README.md. +- Changes to xSQLServerAlwaysOnService + - Get-TargetResource should no longer fail silently with error "Index operation failed; the array index evaluated to null." (issue 519). Now if the Server.IsHadrEnabled property return neither $true or $false the Get-TargetResource function will throw an error. +- Changes to xSQLServerSetUp + - Updated xSQLServerSetup Module Get-Resource method to fix (issue 516 and 490). + - Added change to detect DQ, DQC, BOL, SDK features. Now the function Test-TargetResource returns true after calling set for DQ, DQC, BOL, SDK features (issue 516 and 490). +- Changes to xSQLServerAlwaysOnAvailabilityGroup + - Updated to return the exception raised when an error is thrown. +- Changes to xSQLServerAlwaysOnAvailabilityGroupReplica + - Updated to return the exception raised when an error is thrown. + - Updated parameter description for parameter Name, so that it says it must be in the format SQLServer\InstanceName for named instance (issue 548). - 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. + - Added an optional boolean parameter Disabled. It can be used to enable/disable existing logins or create disabled logins (new logins are created as enabled by default). +- Changes to xSQLServerDatabaseRole + - Updated variable passed to Microsoft.SqlServer.Management.Smo.User constructor to fix issue 530 +- Changes to xSQLServerNetwork + - Added optional parameter SQLServer with default value of $env:COMPUTERNAME (issue 528). + - Added optional parameter RestartTimeout with default value of 120 seconds. + - Now the resource supports restarting a sql server in a cluster (issue 527 and issue 455). + - Now the resource allows to set the parameter TcpDynamicPorts to a blank value (partly fixes issue 534). Setting a blank value for parameter TcpDynamicPorts together with a value for parameter TcpPort means that static port will be used. + - Now the resource will not call Alter() in the Set-TargetResource when there is no change necessary (issue 537). + - Updated example 1-EnableTcpIpOnCustomStaticPort. + - Added unit tests (issue 294). + - Refactored some of the code, cleaned up the rest and fixed PSSA rules warnings (issue 261). + - If parameter TcpDynamicPort is set to "0" at the same time as TcpPort is set the resource will now throw an error (issue 535). + - Added examples (issue 536). + - When TcpDynamicPorts is set to "0" the Test-TargetResource function will no longer fail each time (issue 564). +- Changes to xSQLServerRSConfig + - Replaced sqlcmd.exe usages with Invoke-Sqlcmd calls (issue 567). - 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.' + - Fixed code style, updated README.md and removed *-SqlDatabasePermission functions from xSQLServerHelper.psm1. + - Added the option "GrantWithGrant" with gives the user grant rights, together with the ability to grant others the same right. + - Now the resource can revoke permission correctly (issue 454). When revoking "GrantWithGrant", both the grantee and all the other users the grantee has granted the same permission to, will also get their permission revoked. + - Updated tests to cover Revoke(). +- Changes to xSQLServerHelper + - The missing helper function ("Test-SPDSCObjectHasProperty"), that was referenced in the helper function Test-SQLDscParameterState, is now incorporated into Test-SQLDscParameterState (issue 589). + +' } # End of PSData hashtable @@ -161,3 +126,6 @@ PrivateData = @{ + + + diff --git a/xSQLServerHelper.psm1 b/xSQLServerHelper.psm1 index bc9cd295d..df719bc9f 100644 --- a/xSQLServerHelper.psm1 +++ b/xSQLServerHelper.psm1 @@ -88,52 +88,244 @@ function Connect-SQLAnalysis [CmdletBinding()] param ( - [ValidateNotNull()] + [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] $SQLServer = $env:COMPUTERNAME, - [ValidateNotNull()] + [Parameter()] + [ValidateNotNullOrEmpty()] [System.String] - $SQLInstanceName = "MSSQLSERVER", + $SQLInstanceName = 'MSSQLSERVER', - [ValidateNotNull()] + [Parameter()] + [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $SetupCredential ) $null = [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.AnalysisServices') - if ($SQLInstanceName -eq "MSSQLSERVER") + if ($SQLInstanceName -eq 'MSSQLSERVER') { - $connectSql = $SQLServer + $analysisServiceInstance = $SQLServer } else { - $connectSql = "$SQLServer\$SQLInstanceName" + $analysisServiceInstance = "$SQLServer\$SQLInstanceName" } - $sql = New-Object Microsoft.AnalysisServices.Server - if ($SetupCredential) { $userName = $SetupCredential.GetNetworkCredential().UserName $password = $SetupCredential.GetNetworkCredential().Password - $sql.Connect("Data Source=$connectSql;User ID=$userName;Password=$password") + $analysisServicesDataSource = "Data Source=$analysisServiceInstance;User ID=$userName;Password=$password" } else { - $sql.Connect("Data Source=$connectSql") + $analysisServicesDataSource = "Data Source=$analysisServiceInstance" } - if (!$sql) + try { - Throw -Message "Failed connecting to Analysis Services $connectSql" + $analysisServicesObject = New-Object -TypeName Microsoft.AnalysisServices.Server + if ($analysisServicesObject) + { + $analysisServicesObject.Connect($analysisServicesDataSource) + } + else + { + throw New-TerminatingError -ErrorType AnalysisServicesNoServerObject -ErrorCategory InvalidResult + } + + Write-Verbose -Message "Connected to Analysis Services $analysisServiceInstance." -Verbose + } + catch + { + throw New-TerminatingError -ErrorType AnalysisServicesFailedToConnect -FormatArgs @($analysisServiceInstance) -ErrorCategory ObjectNotFound -InnerException $_.Exception } - New-VerboseMessage -Message "Connected to Analysis Services $connectSql" + return $analysisServicesObject +} - return $sql +<# + .SYNOPSIS + Creates a new application domain and loads the assemblies Microsoft.SqlServer.Smo + for the correct SQL Server major version. + + An isolated application domain is used to load version specific assemblies, this needed + if there is multiple versions of SQL server in the same configuration. So that a newer + version of SQL is not using an older version of the assembly, or vice verse. + + This should be unloaded using the helper function Unregister-SqlAssemblies or + using [System.AppDomain]::Unload($applicationDomainObject). + + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance name to get the major SQL version from. + + .PARAMETER ApplicationDomain + An optional System.AppDomain object to load the assembly into. + + .OUTPUTS + System.AppDomain. Returns the application domain object with SQL SMO loaded. +#> +function Register-SqlSmo +{ + [CmdletBinding()] + [OutputType([System.AppDomain])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $SQLInstanceName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.AppDomain] + $ApplicationDomain + ) + + $sqlMajorVersion = Get-SqlInstanceMajorVersion -SQLInstanceName $SQLInstanceName + New-VerboseMessage -Message ('SQL major version is {0}.' -f $sqlMajorVersion) + + if( -not $ApplicationDomain ) + { + $applicationDomainName = $MyInvocation.MyCommand.ModuleName + New-VerboseMessage -Message ('Creating application domain ''{0}''.' -f $applicationDomainName) + $applicationDomainObject = [System.AppDomain]::CreateDomain($applicationDomainName) + } + else + { + New-VerboseMessage -Message ('Reusing application domain ''{0}''.' -f $ApplicationDomain.FriendlyName) + $applicationDomainObject = $ApplicationDomain + } + + $sqlSmoAssemblyName = "Microsoft.SqlServer.Smo, Version=$sqlMajorVersion.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" + New-VerboseMessage -Message ('Loading assembly ''{0}''.' -f $sqlSmoAssemblyName) + $applicationDomainObject.Load($sqlSmoAssemblyName) | Out-Null + + return $applicationDomainObject +} + +<# + .SYNOPSIS + Creates a new application domain and loads the assemblies Microsoft.SqlServer.Smo and + Microsoft.SqlServer.SqlWmiManagement for the correct SQL Server major version. + + An isolated application domain is used to load version specific assemblies, this needed + if there is multiple versions of SQL server in the same configuration. So that a newer + version of SQL is not using an older version of the assembly, or vice verse. + + This should be unloaded using the helper function Unregister-SqlAssemblies or + using [System.AppDomain]::Unload($applicationDomainObject) preferably in a finally block. + + .PARAMETER SQLInstanceName + String containing the SQL Server Database Engine instance name to get the major SQL version from. + + .PARAMETER ApplicationDomain + An optional System.AppDomain object to load the assembly into. + + .OUTPUTS + System.AppDomain. Returns the application domain object with SQL WMI Management loaded. +#> +function Register-SqlWmiManagement +{ + [CmdletBinding()] + [OutputType([System.AppDomain])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $SQLInstanceName, + + [Parameter()] + [ValidateNotNull()] + [System.AppDomain] + $ApplicationDomain + ) + + $sqlMajorVersion = Get-SqlInstanceMajorVersion -SQLInstanceName $SQLInstanceName + New-VerboseMessage -Message ('SQL major version is {0}.' -f $sqlMajorVersion) + + <# + Must register Microsoft.SqlServer.Smo first because that is a + dependency of Microsoft.SqlServer.SqlWmiManagement. + #> + if (-not $ApplicationDomain) + { + $applicationDomainObject = Register-SqlSmo -SQLInstanceName $SQLInstanceName + } + # Returns zero (0) objects if the assembly is not found + elseif (-not ($ApplicationDomain.GetAssemblies().FullName -match 'Microsoft.SqlServer.Smo')) + { + $applicationDomainObject = Register-SqlSmo -SQLInstanceName $SQLInstanceName -ApplicationDomain $ApplicationDomain + } + + $sqlSqlWmiManagementAssemblyName = "Microsoft.SqlServer.SqlWmiManagement, Version=$sqlMajorVersion.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" + New-VerboseMessage -Message ('Loading assembly ''{0}''.' -f $sqlSqlWmiManagementAssemblyName) + $applicationDomainObject.Load($sqlSqlWmiManagementAssemblyName) | Out-Null + + return $applicationDomainObject +} + +<# + .SYNOPSIS + Unloads all assemblies in an application domain. It unloads the application domain. + + .PARAMETER ApplicationDomain + System.AppDomain object containing the SQL assemblies to unload. +#> +function Unregister-SqlAssemblies +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.AppDomain] + $ApplicationDomain + ) + + New-VerboseMessage -Message ('Unloading application domain ''{0}''.' -f $ApplicationDomain.FriendlyName) + [System.AppDomain]::Unload($ApplicationDomain) +} + +<# + .SYNOPSIS + Returns the major SQL version for the specific instance. + + .PARAMETER SQLInstanceName + String containing the name of the SQL instance to be configured. Default value is 'MSSQLSERVER'. + + .OUTPUTS + System.UInt16. Returns the SQL Server major version number. +#> +function Get-SqlInstanceMajorVersion +{ + [CmdletBinding()] + [OutputType([System.UInt16])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $SQLInstanceName = 'MSSQLSERVER' + ) + + $sqlInstanceId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$SQLInstanceName + $sqlVersion = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$sqlInstanceId\Setup").Version + + if (-not $sqlVersion) + { + throw New-TerminatingError -ErrorType SqlServerVersionIsInvalid -FormatArgs @($SQLInstanceName) -ErrorCategory InvalidResult + } + + [System.UInt16] $sqlMajorVersionNumber = $sqlVersion.Split('.')[0] + + return $sqlMajorVersionNumber } <# @@ -154,7 +346,7 @@ function Connect-SQLAnalysis 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 thrown when the error occurred, which will be added to the final error message. #> function New-TerminatingError { @@ -300,7 +492,7 @@ function New-VerboseMessage This method is used to compare current and desired values for any DSC resource. .PARAMETER CurrentValues - This is hashtable of the current values that are applied to the resource. + This is hash table of the current values that are applied to the resource. .PARAMETER DesiredValues This is a PSBoundParametersDictionary of the desired values for the resource. @@ -334,7 +526,7 @@ function Test-SQLDscParameterState -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)" + "Hash table or CimInstance. Type detected was $($DesiredValues.GetType().Name)" } if (($DesiredValues.GetType().Name -eq "CimInstance") -and ($null -eq $ValuesToCheck)) @@ -356,7 +548,7 @@ function Test-SQLDscParameterState { if (($CurrentValues.ContainsKey($_) -eq $false) ` -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` - -or (($DesiredValues.ContainsKey($_) -eq $true) -and ($DesiredValues.$_.GetType().IsArray))) + -or (($DesiredValues.ContainsKey($_) -eq $true) -and ($null -ne $DesiredValues.$_ -and $DesiredValues.$_.GetType().IsArray))) { if ($DesiredValues.GetType().Name -eq "HashTable" -or ` $DesiredValues.GetType().Name -eq "PSBoundParametersDictionary") @@ -366,7 +558,14 @@ function Test-SQLDscParameterState } else { - $checkDesiredValue = Test-SPDSCObjectHasProperty $DesiredValues $_ + $checkDesiredValue = $false + if (([System.Boolean]($DesiredValues.PSObject.Properties.Name -contains $_)) -eq $true) + { + if ($null -ne $Object.$PropertyName) + { + $checkDesiredValue = $true + } + } } if ($checkDesiredValue) @@ -414,7 +613,7 @@ 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)'") + "and desired state is '$($DesiredValues.$fieldName)'") $returnValue = $false } @@ -519,7 +718,7 @@ function Grant-ServerPerms Connect to a Active Directory and give the Cluster Name Object all rights on the cluster Virtual Computer Object (VCO). .PARAMETER AvailabilityGroupNameListener - String containing the name of the Availabilty Group's Virtual Computer Object (VCO). + String containing the name of the Availability Group's Virtual Computer Object (VCO). .PARAMETER CNO String containing the name of the Cluster Name Object (CNO) for the failover cluster. @@ -549,7 +748,7 @@ function Grant-CNOPerms Try{ $AG = Get-ADComputer $AvailabilityGroupNameListener - $comp = $AG.DistinguishedName # input AD computer distinguishedname + $comp = $AG.DistinguishedName # input AD computer distinguished name $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 @@ -572,10 +771,10 @@ function Grant-CNOPerms <# .SYNOPSIS - Create a new computer object for a Availabilty Group's Virtual Computer Object (VCO). + Create a new computer object for a Availability Group's Virtual Computer Object (VCO). .PARAMETER AvailabilityGroupNameListener - String containing the name of the Availabilty Group's Virtual Computer Object (VCO). + String containing the name of the Availability Group's Virtual Computer Object (VCO). .PARAMETER SQLServer String containing the host name of the SQL Server to connect to. @@ -685,899 +884,262 @@ function New-ListenerADObject .SYNOPSIS Imports the module SQLPS in a standardized way. #> -function Import-SQLPSModule { +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 + $module = (Get-Module -FullyQualifiedName 'SqlServer' -ListAvailable).Name + if ($module) + { + New-VerboseMessage -Message 'Preferred module SqlServer found.' + } + else + { + New-VerboseMessage -Message 'Module SqlServer not found, trying to use older SQLPS module.' + $module = (Get-Module -FullyQualifiedName 'SQLPS' -ListAvailable).Name } - Write-Debug "SQLPS module changes CWD to SQLSERVER:\ when loading, pushing location to pop it when module is loaded." - Push-Location + if ($module) + { + try + { + Write-Debug -Message '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." - } - catch { - throw New-TerminatingError -ErrorType FailedToImportSQLPSModule -ErrorCategory InvalidOperation -InnerException $_.Exception + New-VerboseMessage -Message ('Importing {0} module.' -f $module) + + <# + SQLPS has unapproved verbs, disable checking to ignore Warnings. + Suppressing verbose so all cmdlet is not listed. + #> + Import-Module -Name $module -DisableNameChecking -Verbose:$False -ErrorAction Stop + + Write-Debug -Message ('Module {0} imported.' -f $module) + } + catch + { + throw New-TerminatingError -ErrorType FailedToImportSqlModule -FormatArgs @($module) -ErrorCategory InvalidOperation -InnerException $_.Exception + } + finally + { + Write-Debug -Message 'Popping location back to what it was before importing SQLPS module.' + Pop-Location + } } - finally { - Write-Debug "Popping location back to what it was before importing SQLPS module." - Pop-Location + else + { + throw New-TerminatingError -ErrorType SqlModuleNotFound -ErrorCategory InvalidOperation -InnerException $_.Exception } - } <# .SYNOPSIS - Returns the SQL Server instance name in the way SQLPS Provider expect it. - - .DESCRIPTION - 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. + Restarts a SQL Server instance and associated services - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to validate. -#> -function Get-SQLPSInstanceName -{ - [CmdletBinding()] - [OutputType([String])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName - ) + .PARAMETER SQLServer + Hostname of the SQL Server to be configured - if( $InstanceName -eq "MSSQLSERVER" ) { - $InstanceName = "DEFAULT" - } + .PARAMETER SQLInstanceName + Name of the SQL instance to be configured. Default is 'MSSQLSERVER' - return $InstanceName -} + .PARAMETER Timeout + Timeout value for restarting the SQL services. The default value is 120 seconds. -<# - .SYNOPSIS - Returns the SQL Server SQLPS provider server object. + .EXAMPLE + Restart-SqlService -SQLServer localhost - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to connect to. + .EXAMPLE + Restart-SqlService -SQLServer localhost -SQLInstanceName 'NamedInstance' - .PARAMETER NodeName - String containing the host name of the SQL Server to connect to. + .EXAMPLE + Restart-SqlService -SQLServer CLU01 -Timeout 300 #> -function Get-SQLPSInstance +function Restart-SqlService { [CmdletBinding()] - [OutputType([String])] param ( [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter(Mandatory = $true)] - [System.String] - $NodeName - ) - - $InstanceName = Get-SQLPSInstanceName -InstanceName $InstanceName - $Path = "SQLSERVER:\SQL\$NodeName\$InstanceName" + [String] + $SQLServer, - New-VerboseMessage -Message "Connecting to $Path as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" + [Parameter()] + [String] + $SQLInstanceName = 'MSSQLSERVER', - Import-SQLPSModule - $instance = Get-Item $Path + [Parameter()] + [Int32] + $Timeout = 120 + ) - return $instance -} + ## Connect to the instance + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName -<# - .SYNOPSIS - Returns the SQL Server SQLPS provider endpoint object. + 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'" | + Where-Object { $_.PrivateProperties.InstanceName -eq $serverObject.ServiceName } - .PARAMETER Name - String containing the name of the endpoint to return. + New-VerboseMessage -Message 'Getting active cluster resource SQL Server Agent' + $agentService = $sqlService | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | + Where-Object { ($_.Type -eq "SQL Server Agent") -and ($_.State -eq 2) } - .PARAMETER InstanceName - String containing the SQL Server Database Engine instance to connect to. + ## Build a listing of resources being acted upon + $resourceNames = @($sqlService.Name, ($agentService | Select-Object -ExpandProperty Name)) -join "," - .PARAMETER NodeName - String containing the host name of the SQL Server to connect to. -#> -function Get-SQLAlwaysOnEndpoint -{ - [CmdletBinding()] - [OutputType()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, + ## Stop the SQL Server and dependent resources + New-VerboseMessage -Message "Bringing the SQL Server resources $resourceNames offline." + $sqlService | Invoke-CimMethod -MethodName TakeOffline -Arguments @{ Timeout = $Timeout } - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, + ## Start the SQL server resource + New-VerboseMessage -Message 'Bringing the SQL Server resource back online.' + $sqlService | Invoke-CimMethod -MethodName BringOnline -Arguments @{ Timeout = $Timeout } - [Parameter(Mandatory = $true)] - [System.String] - $NodeName - ) + ## Start the SQL Agent resource + if ($agentService) + { + New-VerboseMessage -Message 'Bringing the SQL Server Agent resource online.' + $agentService | Invoke-CimMethod -MethodName BringOnline -Arguments @{ Timeout = $Timeout } + } + } + else + { + New-VerboseMessage -Message 'Getting SQL Service information' + $sqlService = Get-Service -DisplayName "SQL Server ($($serverObject.ServiceName))" - $instance = Get-SQLPSInstance -InstanceName $InstanceName -NodeName $NodeName - $Path = "$($instance.PSPath)\Endpoints" + ## Get all dependent services that are running. + ## There are scenarios where an automatic service is stopped and should not be restarted automatically. + $agentService = $sqlService.DependentServices | Where-Object { $_.Status -eq "Running" } - Write-Debug "Connecting to $Path as $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" + ## Restart the SQL Server service + New-VerboseMessage -Message 'SQL Server service restarting' + $sqlService | Restart-Service -Force - [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 + ## Start dependent services + $agentService | ForEach-Object { + New-VerboseMessage -Message "Starting $($_.DisplayName)" + $_ | Start-Service + } } - - return $endpoint } <# .SYNOPSIS - Add a user to a server role in the SQL Server instance provided. + Executes a query on the specified database. - .PARAMETER Sql - An object returned from Connect-SQL function. + .PARAMETER SQLServer + The hostname of the server that hosts the SQL instance. - .PARAMETER LoginName - String containing the login (user) which should be added as a member to the server role. + .PARAMETER SQLInstanceName + The name of the SQL instance that hosts the database. - .PARAMETER ServerRole - String containing the name of the server role which the user will be added as a member to. -#> -function Add-SqlServerRoleMember -{ - [CmdletBinding()] - param - ( - [ValidateNotNull()] - [System.Object] - $Sql, - - [ValidateNotNull()] - [System.String] - $LoginName, - - [ValidateNotNull()] - [System.String[]] - $ServerRole - - ) - - $sqlRole = $Sql.Roles - if ($sqlRole) - { - try - { - foreach ($currentServerRole in $ServerRole) - { - New-VerboseMessage -Message "Adding SQL login $LoginName in role $currentServerRole" - $sqlRole[$currentServerRole].AddMember($LoginName) - } - } - catch - { - New-VerboseMessage -Message "Failed adding SQL login $LoginName in role $currentServerRole" - } - } - else - { - New-VerboseMessage -Message "Failed to getting SQL server roles" - } -} - -<# - .SYNOPSIS - Remove a user in a server role in the SQL Server instance provided. - - .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 ServerRole - String containing the name of the server role for which the user will be removed as a member. -#> -function Remove-SqlServerRoleMember -{ - [CmdletBinding()] - param - ( - [ValidateNotNull()] - [System.Object] - $Sql, - - [ValidateNotNull()] - [System.String] - $LoginName, - - [ValidateNotNull()] - [System.String[]] - $ServerRole - - ) - - $sqlRole = $Sql.Roles - if ($sqlRole) - { - try - { - foreach ($currentServerRole in $ServerRole) - { - New-VerboseMessage -Message "Deleting SQL login $LoginName in role $currentServerRole" - $sqlRole[$currentServerRole].DropMember($LoginName) - } - } - catch - { - New-VerboseMessage -Message "Failed deleting SQL login $LoginName in role $currentServerRole" - } - } - else - { - New-VerboseMessage -Message "Failed to getting SQL server roles" - } -} - -<# - .SYNOPSIS - This validates if a user is a member of a server role. - 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 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. -#> -function Confirm-SqlServerRoleMember -{ - [CmdletBinding()] - param - ( - [ValidateNotNull()] - [System.Object] - $Sql, - - [ValidateNotNull()] - [System.String] - $LoginName, - - [ValidateNotNull()] - [System.String[]] - $ServerRole - - ) - - $sqlRole = $Sql.Roles - if ($sqlRole) - { - foreach ($currentServerRole in $ServerRole) - { - if ($sqlRole[$currentServerRole]) - { - $membersInRole = $sqlRole[$currentServerRole].EnumMemberNames() - if ($membersInRole.Contains($LoginName)) - { - $confirmServerRole = $true - New-VerboseMessage -Message "$LoginName is present in SQL role name $currentServerRole" - } - else - { - New-VerboseMessage -Message "$LoginName is absent in SQL role name $currentServerRole" - $confirmServerRole = $false - } - } - else - { - New-VerboseMessage -Message "SQL role name $currentServerRole is absent" - $confirmServerRole = $false - } - } - } - else - { - New-VerboseMessage -Message "Failed getting SQL roles" - $confirmServerRole = $false - } - - return $confirmServerRole -} - -<# - .SYNOPSIS - Restarts a SQL Server instance and associated services - - .PARAMETER SQLServer - Hostname of the SQL Server to be configured - - .PARAMETER SQLInstanceName - Name of the SQL instance to be configued. Default is 'MSSQLSERVER' + .PARAMETER Database + Specify the name of the database to execute the query on. - .PARAMETER Timeout - Timeout value for restarting the SQL services. The default value is 120 seconds. + .PARAMETER Query + The query string to execute. - .EXAMPLE - Restart-SqlService -SQLServer localhost + .PARAMETER WithResults + Specifies if the query should return results. .EXAMPLE - Restart-SqlService -SQLServer localhost -SQLInstanceName 'NamedInstance' + Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master -Query 'SELECT name FROM sys.databases' -WithResults .EXAMPLE - Restart-SqlService -SQLServer CLU01 -Timeout 300 + Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master -Query 'RESTORE DATABASE [NorthWinds] WITH RECOVERY' #> -function Restart-SqlService +function Invoke-Query { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] [String] $SQLServer, - [Parameter()] - [String] - $SQLInstanceName = 'MSSQLSERVER', - - [Parameter()] - [Int32] - $Timeout = 120 - ) - - ## Connect to the instance - $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - - 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'" | - Where-Object { $_.PrivateProperties.InstanceName -eq $serverObject.ServiceName } - - New-VerboseMessage -Message 'Getting active cluster resource SQL Server Agent' - $agentService = $sqlService | Get-CimAssociatedInstance -ResultClassName MSCluster_Resource | - Where-Object { ($_.Type -eq "SQL Server Agent") -and ($_.State -eq 2) } - - ## Build a listing of resources being acted upon - $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." - $sqlService | Invoke-CimMethod -MethodName TakeOffline -Arguments @{ Timeout = $Timeout } - - ## Start the SQL server resource - New-VerboseMessage -Message 'Bringing the SQL Server resource back online.' - $sqlService | Invoke-CimMethod -MethodName BringOnline -Arguments @{ Timeout = $Timeout } - - ## Start the SQL Agent resource - if ($agentService) - { - New-VerboseMessage -Message 'Bringing the SQL Server Agent resource online.' - $agentService | Invoke-CimMethod -MethodName BringOnline -Arguments @{ Timeout = $Timeout } - } - } - else - { - New-VerboseMessage -Message 'Getting SQL Service information' - $sqlService = Get-Service -DisplayName "SQL Server ($($serverObject.ServiceName))" - - ## Get all dependent services that are running. - ## There are scenarios where an automatic service is stopped and should not be restarted automatically. - $agentService = $sqlService.DependentServices | Where-Object { $_.Status -eq "Running" } - - ## Restart the SQL Server service - New-VerboseMessage -Message 'SQL Server service restarting' - $sqlService | Restart-Service -Force - - ## Start dependent services - $agentService | ForEach-Object { - New-VerboseMessage -Message "Starting $($_.DisplayName)" - $_ | Start-Service - } - } -} - -<# - .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 + [String] + $SQLInstanceName, - .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, + [String] + $Database, [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $DatabaseName, + [String] + $Query, - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [ValidateSet('Full','Simple','BulkLogged')] - [System.String] - $RecoveryModel + [Parameter()] + [Switch] + $WithResults ) - Write-Verbose -Message "Setting the recovery model for the database $DatabaseName" - $sqlDatabase = $SqlServerObject.Databases[$DatabaseName] - $sqlInstanceName = $SqlServerObject.InstanceName - $sqlServer = $SqlServerObject.ComputerNamePhysicalNetBIOS + $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName - if ($sqlDatabase) + if ( $WithResults ) { - if($sqlDatabase.RecoveryModel -ne $RecoveryModel) + try + { + $result = $serverObject.Databases[$Database].ExecuteWithResults($Query) + } + catch { - $sqlDatabase.RecoveryModel = $RecoveryModel - $sqlDatabase.Alter() - New-VerboseMessage -Message "The recovery model for the database $DatabaseName is changed to '$RecoveryModel'." + throw New-TerminatingError -ErrorType ExecuteQueryWithResultsFailed -FormatArgs $Database -ErrorCategory NotSpecified } } 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) + try { - 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 - } - } - } + $serverObject.Databases[$Database].ExecuteNonQuery($Query) } - else + catch { - throw New-TerminatingError -ErrorType LoginNotFound ` - -FormatArgs @($Name,$sqlServer,$sqlInstanceName) ` - -ErrorCategory ObjectNotFound + throw New-TerminatingError -ErrorType ExecuteNonQueryFailed -FormatArgs $Database -ErrorCategory NotSpecified } } - else - { - throw New-TerminatingError -ErrorType NoDatabase ` - -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` - -ErrorCategory InvalidResult - } - $permission + return $result } <# .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 + Executes the alter method on an Availability Group Replica object. - .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 + .PARAMETER AvailabilityGroupReplica + The Availability Group Replica object that must be altered. #> -function Add-SqlDatabasePermission +function Update-AvailabilityGroupReplica { - [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 + [Microsoft.SqlServer.Management.Smo.AvailabilityReplica] + $AvailabilityGroupReplica ) - 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 + try { - throw New-TerminatingError -ErrorType NoDatabase ` - -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` - -ErrorCategory InvalidResult + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + $AvailabilityGroupReplica.Alter() } -} - -<# - .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) + catch { - 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 - } + throw New-TerminatingError -ErrorType AlterAvailabilityGroupReplicaFailed -FormatArgs $AvailabilityGroupReplica.Name -ErrorCategory OperationStopped } - else + finally { - throw New-TerminatingError -ErrorType NoDatabase ` - -FormatArgs @($Database,$sqlServer,$sqlInstanceName) ` - -ErrorCategory InvalidResult + $ErrorActionPreference = $originalErrorActionPreference } } -<# - .SYNOPSIS - Executes a query on the specified database. - - .PARAMETER SQLServer - The hostname of the server that hosts the SQL instance. - - .PARAMETER SQLInstanceName - The name of the SQL instance that hosts the database. - - .PARAMETER Database - Specify the name of the database to execute the query on. - - .PARAMETER Query - The query string to execute. - - .PARAMETER WithResults - Specifies if the query should return results. - - .EXAMPLE - Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master -Query 'SELECT name FROM sys.databases' -WithResults - - .EXAMPLE - Invoke-Query -SQLServer Server1 -SQLInstanceName MSSQLSERVER -Database master -Query 'RESTORE DATABASE [NorthWinds] WITH RECOVERY' -#> -function Invoke-Query +function Test-LoginEffectivePermissions { - [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -1590,42 +1152,48 @@ function Invoke-Query $SQLInstanceName, [Parameter(Mandatory = $true)] - [String] - $Database, + [ValidateNotNullOrEmpty()] + [string] + $LoginName, [Parameter(Mandatory = $true)] - [String] - $Query, - - [Parameter()] - [Switch] - $WithResults + [string[]] + $Permissions ) - $serverObject = Connect-SQL -SQLServer $SQLServer -SQLInstanceName $SQLInstanceName + # Assume the permissions are not present + $permissionsPresent = $false - if ( $WithResults ) - { - try - { - $result = $serverObject.Databases[$Database].ExecuteWithResults($Query) - } - catch - { - throw New-TerminatingError -ErrorType ExecuteQueryWithResultsFailed -FormatArgs $Database -ErrorCategory NotSpecified - } + $invokeQueryParams = @{ + SQLServer = $SQLServer + SQLInstanceName = $SQLInstanceName + Database = 'master' + WithResults = $true } - else + + $queryToGetEffectivePermissionsForLogin = " + EXECUTE AS LOGIN = '$LoginName' + SELECT DISTINCT permission_name + FROM fn_my_permissions(null,'SERVER') + REVERT + " + + New-VerboseMessage -Message "Getting the effective permissions for the login '$LoginName' on '$sqlInstanceName'." + + $loginEffectivePermissionsResult = Invoke-Query @invokeQueryParams -Query $queryToGetEffectivePermissionsForLogin + $loginEffectivePermissions = $loginEffectivePermissionsResult.Tables.Rows.permission_name + + if ( $null -ne $loginEffectivePermissions ) { - try - { - $serverObject.Databases[$Database].ExecuteNonQuery($Query) - } - catch + $loginMissingPermissions = Compare-Object -ReferenceObject $Permissions -DifferenceObject $loginEffectivePermissions | + Where-Object -FilterScript { $_.SideIndicator -ne '=>' } | + Select-Object -ExpandProperty InputObject + + if ( $loginMissingPermissions.Count -eq 0 ) { - throw New-TerminatingError -ErrorType ExecuteNonQueryFailed -FormatArgs $Database -ErrorCategory NotSpecified + $permissionsPresent = $true } } - return $result + return $permissionsPresent }