diff --git a/CHANGELOG.md b/CHANGELOG.md index f2afd8d5e..25f55a652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,11 @@ - Removed the local specific common test for compiling examples in this repository and instead opted-in for the common test in the 'DscResource.Tests' repository ([issue #669](https://github.com/PowerShell/SqlServerDsc/issues/669)). + - Added new resource SqlServerDatabaseMail for configuring SQL Server + Database Mail. + - Updated the helper function Test-SQLDscParameterState to handle the + data type UInt16. + - Fixed typo in SqlServerDscCommon.Tests. - Changes to SqlAlias - Fixed issue where exception was thrown if reg keys did not exist ([issue #949](https://github.com/PowerShell/SqlServerDsc/issues/949)). diff --git a/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.psm1 b/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.psm1 new file mode 100644 index 000000000..a96bc07cc --- /dev/null +++ b/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.psm1 @@ -0,0 +1,765 @@ +Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) ` + -ChildPath 'SqlServerDscHelper.psm1') ` + -Force + +Import-Module -Name (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlServerDatabaseMail' + +<# + .SYNOPSIS + Returns the current state of the Database Mail configuration. + + .PARAMETER ServerName + The hostname of the SQL Server to be configured. + Defaults to $env:COMPUTERNAME. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER AccountName + The name of the Database Mail account. + + .PARAMETER EmailAddress + The e-mail address from which mail will originate. + + .PARAMETER MailServerName + The fully qualified domain name of the mail server name to which e-mail are + sent. + + .PARAMETER ProfileName + The profile name of the Database Mail. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $AccountName, + + [Parameter(Mandatory = $true)] + [System.String] + $EmailAddress, + + [Parameter(Mandatory = $true)] + [System.String] + $MailServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $ProfileName + ) + + $returnValue = @{ + Ensure = 'Absent' + ServerName = $ServerName + InstanceName = $InstanceName + AccountName = $null + EmailAddress = $null + MailServerName = $null + LoggingLevel = $null + ProfileName = $null + DisplayName = $null + ReplyToAddress = $null + Description = $null + TcpPort = $null + } + + Write-Verbose -Message ( + $script:localizedData.ConnectToSqlInstance ` + -f $ServerName, $InstanceName + ) + + $sqlServerObject = Connect-SQL -SQLServer $ServerName -SQLInstanceName $InstanceName + + if ($sqlServerObject) + { + $databaseMailEnabledRunValue = $sqlServerObject.Configuration.DatabaseMailEnabled.RunValue + if ($databaseMailEnabledRunValue -eq 1) + { + Write-Verbose -Message ( + $script:localizedData.DatabaseMailEnabled ` + -f $databaseMailEnabledRunValue + ) + + $databaseMail = $sqlServerObject.Mail + + $databaseMailAccount = $databaseMail.Accounts | Where-Object -FilterScript { + $_.Name -eq $AccountName + } + + if ($databaseMailAccount) + { + Write-Verbose -Message ( + $script:localizedData.GetConfiguration ` + -f $AccountName + ) + + $loggingLevelText = switch ($databaseMail.ConfigurationValues['LoggingLevel'].Value) + { + 1 + { + 'Normal' + } + + 2 + { + 'Extended' + } + + 3 + { + 'Verbose' + } + } + + <# + AccountName exist so we set this as 'Present' regardless if + other properties are in desired state, the Test-TargetResource + function must handle that. + #> + $returnValue['Ensure'] = 'Present' + $returnValue['LoggingLevel'] = $loggingLevelText + $returnValue['AccountName'] = $databaseMailAccount.Name + $returnValue['EmailAddress'] = $databaseMailAccount.EmailAddress + $returnValue['DisplayName'] = $databaseMailAccount.DisplayName + $returnValue['ReplyToAddress'] = $databaseMailAccount.ReplyToAddress + + # Currently only the first mail server is handled. + $mailServer = $databaseMailAccount.MailServers | Select-Object -First 1 + + $returnValue['MailServerName'] = $mailServer.Name + $returnValue['TcpPort'] = $mailServer.Port + + # Currently only one profile is handled, so this make sure only the first string (profile name) is returned. + $returnValue['ProfileName'] = $databaseMail.Profiles | Select-Object -First 1 -ExpandProperty Name + + # SQL Server returns '' for Description property when value is not set. + if ($databaseMailAccount.Description -eq '') + { + # Convert empty value to $null + $returnValue['Description'] = $null + } + else + { + $returnValue['Description'] = $databaseMailAccount.Description + } + } + else + { + Write-Verbose -Message ( + $script:localizedData.AccountIsMissing ` + -f $AccountName + ) + } + } + else + { + Write-Verbose -Message ( + $script:localizedData.DatabaseMailDisabled + ) + } + } + + return $returnValue +} + +<# + .SYNOPSIS + Creates or removes the Database Mail configuration. + + .PARAMETER Ensure + Specifies the desired state of the Database Mail. + When set to 'Present', the Database Mail will be created. + When set to 'Absent', the Database Mail will be removed. + Default value is 'Present'. + + .PARAMETER ServerName + The hostname of the SQL Server to be configured. + Defaults to $env:COMPUTERNAME. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER AccountName + The name of the Database Mail account. + + .PARAMETER EmailAddress + The e-mail address from which mail will originate. + + .PARAMETER MailServerName + The fully qualified domain name of the mail server name to which e-mail are + sent. + + .PARAMETER ProfileName + The profile name of the Database Mail. + + .PARAMETER DisplayName + The display name of the outgoing mail server. Default value is the same + value assigned to parameter MailServerName. + + .PARAMETER ReplyToAddress + The e-mail address to which the receiver of e-mails will reply to. + Default value is the same e-mail address assigned to parameter EmailAddress. + + .PARAMETER Description + The description of the Database Mail. + + .PARAMETER LoggingLevel + The logging level that the Database Mail will use. If not specified the + default logging level is 'Extended'. { Normal | *Extended* | Verbose }. + + .PARAMETER TcpPort + The TCP port used for communication. Default value is port 25. + + .NOTES + Information about the different properties can be found here + https://docs.microsoft.com/en-us/sql/relational-databases/database-mail/configure-database-mail. + +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $AccountName, + + [Parameter(Mandatory = $true)] + [System.String] + $EmailAddress, + + [Parameter(Mandatory = $true)] + [System.String] + $MailServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $ProfileName, + + [Parameter()] + [System.String] + $DisplayName = $MailServerName, + + [Parameter()] + [System.String] + $ReplyToAddress = $EmailAddress, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + [ValidateSet('Normal', 'Extended', 'Verbose')] + $LoggingLevel, + + [Parameter()] + [System.UInt16] + $TcpPort = 25 + ) + + Write-Verbose -Message ( + $script:localizedData.ConnectToSqlInstance ` + -f $ServerName, $InstanceName + ) + + $sqlServerObject = Connect-SQL -SQLServer $ServerName -SQLInstanceName $InstanceName + + if ($sqlServerObject) + { + if ($Ensure -eq 'Present') + { + + $databaseMailEnabledRunValue = $sqlServerObject.Configuration.DatabaseMailEnabled.RunValue + if ($databaseMailEnabledRunValue -ne 1) + { + Write-Verbose -Message ( + $script:localizedData.EnablingDatabaseMail ` + -f $databaseMailEnabledRunValue, $ServerName, $InstanceName + ) + + $sqlServerObject.Configuration.DatabaseMailEnabled.ConfigValue = 1 + $sqlServerObject.Configuration.Alter() + + # Set $databaseMailEnabledRunValue to the updated value. + $databaseMailEnabledRunValue = $sqlServerObject.Configuration.DatabaseMailEnabled.RunValue + + Write-Verbose -Message ( + $script:localizedData.DatabaseMailEnabled ` + -f $databaseMailEnabledRunValue + ) + } + + if ($databaseMailEnabledRunValue -eq 1) + { + $databaseMail = $sqlServerObject.Mail + + if ($PSBoundParameters.ContainsKey('LoggingLevel')) + { + $loggingLevelValue = switch ($LoggingLevel) + { + 'Normal' + { + 1 + } + + 'Extended' + { + 2 + } + + 'Verbose' + { + 3 + } + } + + $currentLoggingLevelValue = $databaseMail.ConfigurationValues['LoggingLevel'].Value + if ($loggingLevelValue -ne $currentLoggingLevelValue) + { + Write-Verbose -Message ( + $script:localizedData.ChangingLoggingLevel ` + -f $LoggingLevel, $loggingLevelValue + ) + + $databaseMail.ConfigurationValues['LoggingLevel'].Value = $loggingLevelValue + $databaseMail.ConfigurationValues['LoggingLevel'].Alter() + } + else + { + Write-Verbose -Message ( + $script:localizedData.CurrentLoggingLevel ` + -f $LoggingLevel, $loggingLevelValue + ) + } + } + + $databaseMailAccount = $databaseMail.Accounts | Where-Object -FilterScript { + $_.Name -eq $AccountName + } + + if (-not $databaseMailAccount) + { + Write-Verbose -Message ( + $script:localizedData.CreatingMailAccount ` + -f $AccountName + ) + + $databaseMailAccount = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Mail.MailAccount -ArgumentList @($databaseMail, $AccountName) + $databaseMailAccount.Description = $Description + $databaseMailAccount.DisplayName = $DisplayName + $databaseMailAccount.EmailAddress = $EmailAddress + $databaseMailAccount.ReplyToAddress = $ReplyToAddress + $databaseMailAccount.Create() + + # The previous Create() method will always create a first mail server. + $mailServer = $databaseMailAccount.MailServers | Select-Object -First 1 + + if ($mailServer) + { + $mailServer.Rename($MailServerName) + + if ($PSBoundParameters.ContainsKey('TcpPort')) + { + $mailServer.Port = $TcpPort + } + + $mailServer.Alter() + } + } + else + { + Write-Verbose -Message ( + $script:localizedData.MailAccountExist ` + -f $AccountName + ) + + $currentDisplayName = $databaseMailAccount.DisplayName + if ($currentDisplayName -ne $DisplayName) + { + Write-Verbose -Message ( + $script:localizedData.UpdatingPropertyOfMailServer -f @( + $currentDisplayName + $DisplayName + $script:localizedData.MailServerPropertyDisplayName + ) + ) + + $databaseMailAccount.DisplayName = $DisplayName + $databaseMailAccount.Alter() + } + + $currentDescription = $databaseMailAccount.Description + if ($currentDescription -ne $Description) + { + Write-Verbose -Message ( + $script:localizedData.UpdatingPropertyOfMailServer -f @( + $currentDescription + $Description + $script:localizedData.MailServerPropertyDescription + ) + ) + + $databaseMailAccount.Description = $Description + $databaseMailAccount.Alter() + } + + $currentEmailAddress = $databaseMailAccount.EmailAddress + if ($currentEmailAddress -ne $EmailAddress) + { + Write-Verbose -Message ( + $script:localizedData.UpdatingPropertyOfMailServer -f @( + $currentEmailAddress + $EmailAddress + $script:localizedData.MailServerPropertyEmailAddress + ) + ) + + $databaseMailAccount.EmailAddress = $EmailAddress + $databaseMailAccount.Alter() + } + + $currentReplyToAddress = $databaseMailAccount.ReplyToAddress + if ($currentReplyToAddress -ne $ReplyToAddress) + { + Write-Verbose -Message ( + $script:localizedData.UpdatingPropertyOfMailServer -f @( + $currentReplyToAddress + $ReplyToAddress + $script:localizedData.MailServerPropertyReplyToEmailAddress + ) + ) + + $databaseMailAccount.ReplyToAddress = $ReplyToAddress + $databaseMailAccount.Alter() + } + + $mailServer = $databaseMailAccount.MailServers | Select-Object -First 1 + + $currentMailServerName = $mailServer.Name + if ($currentMailServerName -ne $MailServerName) + { + Write-Verbose -Message ( + $script:localizedData.UpdatingPropertyOfMailServer -f @( + $currentMailServerName + $MailServerName + $script:localizedData.MailServerPropertyServerName + ) + ) + + $mailServer.Rename($MailServerName) + $mailServer.Alter() + } + + $currentTcpPort = $mailServer.Port + if ($currentTcpPort -ne $TcpPort) + { + Write-Verbose -Message ( + $script:localizedData.UpdatingPropertyOfMailServer -f @( + $currentTcpPort + $TcpPort + $script:localizedData.MailServerPropertyTcpPort + ) + ) + + $mailServer.Port = $TcpPort + $mailServer.Alter() + } + } + + $databaseMailProfile = $databaseMail.Profiles | Where-Object -FilterScript { + $_.Name -eq $ProfileName + } + + if (-not $databaseMailProfile) + { + Write-Verbose -Message ( + $script:localizedData.CreatingMailProfile ` + -f $ProfileName + ) + + $databaseMailProfile = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Mail.MailProfile -ArgumentList @($databaseMail, $ProfileName) + $databaseMailProfile.Description = $Description + $databaseMailProfile.Create() + + <# + A principal refers to a database user, a database role or + server role, an application role, or a SQL Server login. + You can add these types of users to the mail profile. + https://msdn.microsoft.com/en-us/library/ms208094.aspx + #> + $databaseMailProfile.AddPrincipal('public', $true) # $true means the default profile. + $databaseMailProfile.AddAccount($AccountName, 0) # Sequence number zero (0). + $databaseMailProfile.Alter() + } + else + { + Write-Verbose -Message ( + $script:localizedData.MailProfileExist ` + -f $ProfileName + ) + } + + if ($sqlServerObject.JobServer.AgentMailType -ne 'DatabaseMail' -or $sqlServerObject.JobServer.DatabaseMailProfile -ne $ProfileName) + { + Write-Verbose -Message ( + $script:localizedData.ConfigureSqlAgent + ) + + $sqlServerObject.JobServer.AgentMailType = 'DatabaseMail' + $sqlServerObject.JobServer.DatabaseMailProfile = $ProfileName + $sqlServerObject.JobServer.Alter() + } + else + { + Write-Verbose -Message ( + $script:localizedData.SqlAgentAlreadyConfigured + ) + } + } + else + { + $errorMessage = $script:localizedData.DatabaseMailDisabled + New-InvalidOperationException -Message $errorMessage + } + } + else + { + if ($sqlServerObject.JobServer.AgentMailType -eq 'DatabaseMail' -or $sqlServerObject.JobServer.DatabaseMailProfile -eq $ProfileName) + { + Write-Verbose -Message ( + $script:localizedData.RemovingSqlAgentConfiguration + ) + + $sqlServerObject.JobServer.AgentMailType = 'SqlAgentMail' + $sqlServerObject.JobServer.DatabaseMailProfile = $null + $sqlServerObject.JobServer.Alter() + } + + $databaseMail = $sqlServerObject.Mail + + $databaseMailProfile = $databaseMail.Profiles | Where-Object -FilterScript { + $_.Name -eq $ProfileName + } + + if ($databaseMailProfile) + { + Write-Verbose -Message ( + $script:localizedData.RemovingMailProfile ` + -f $ProfileName + ) + + $databaseMailProfile.Drop() + } + + $databaseMailAccount = $databaseMail.Accounts | Where-Object -FilterScript { + $_.Name -eq $AccountName + } + + if ($databaseMailAccount) + { + Write-Verbose -Message ( + $script:localizedData.RemovingMailAccount ` + -f $AccountName + ) + + $databaseMailAccount.Drop() + } + + $databaseMailEnabledRunValue = $sqlServerObject.Configuration.DatabaseMailEnabled.RunValue + if ($databaseMailEnabledRunValue -eq 1) + { + $sqlServerObject.Configuration.DatabaseMailEnabled.ConfigValue = 0 + $sqlServerObject.Configuration.Alter() + + # Set $databaseMailEnabledRunValue to the updated value. + $databaseMailEnabledRunValue = $sqlServerObject.Configuration.DatabaseMailEnabled.RunValue + + Write-Verbose -Message ( + $script:localizedData.DatabaseMailDisabled + ) + } + } + } +} + +<# + .SYNOPSIS + Determines if the Database Mail is in the desired state. + + .PARAMETER Ensure + Specifies the desired state of the Database Mail. + When set to 'Present', the Database Mail will be created. + When set to 'Absent', the Database Mail will be removed. + Default value is 'Present'. + + .PARAMETER ServerName + The hostname of the SQL Server to be configured. + Defaults to $env:COMPUTERNAME. + + .PARAMETER InstanceName + The name of the SQL instance to be configured. + + .PARAMETER AccountName + The name of the Database Mail account. + + .PARAMETER EmailAddress + The e-mail address from which mail will originate. + + .PARAMETER MailServerName + The fully qualified domain name of the mail server name to which e-mail are + sent. + + .PARAMETER ProfileName + The profile name of the Database Mail. + + .PARAMETER DisplayName + The display name of the outgoing mail server. Default value is the same + value assigned to parameter MailServerName. + + .PARAMETER ReplyToAddress + The e-mail address to which the receiver of e-mails will reply to. + Default value is the same e-mail address assigned to parameter EmailAddress. + + .PARAMETER Description + The description of the Database Mail. + + .PARAMETER LoggingLevel + The logging level that the Database Mail will use. If not specified the + default logging level is 'Extended'. { Normal | *Extended* | Verbose }. + + .PARAMETER TcpPort + The TCP port used for communication. Default value is port 25. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $AccountName, + + [Parameter()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [System.String] + $EmailAddress, + + [Parameter(Mandatory = $true)] + [System.String] + $MailServerName, + + [Parameter(Mandatory = $true)] + [System.String] + $ProfileName, + + [Parameter()] + [System.String] + $DisplayName = $MailServerName, + + [Parameter()] + [System.String] + $ReplyToAddress = $EmailAddress, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + [ValidateSet('Normal', 'Extended', 'Verbose')] + $LoggingLevel, + + [Parameter()] + [System.UInt16] + $TcpPort = 25 + ) + + $getTargetResourceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + AccountName = $AccountName + EmailAddress = $EmailAddress + MailServerName = $MailServerName + ProfileName = $ProfileName + } + + $returnValue = $false + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + Write-Verbose -Message ( + $script:localizedData.TestingConfiguration + ) + + if ($Ensure -eq 'Present') + { + $returnValue = Test-SQLDscParameterState ` + -CurrentValues $getTargetResourceResult ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @( + 'AccountName' + 'EmailAddress' + 'MailServerName' + 'ProfileName' + 'Ensure' + 'ReplyToAddress' + 'TcpPort' + 'DisplayName' + 'Description' + 'LoggingLevel' + ) + } + else + { + if ($Ensure -eq $getTargetResourceResult.Ensure) + { + $returnValue = $true + } + } + + return $returnValue +} diff --git a/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.schema.mof b/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.schema.mof new file mode 100644 index 000000000..35f803f30 --- /dev/null +++ b/DSCResources/MSFT_SqlServerDatabaseMail/MSFT_SqlServerDatabaseMail.schema.mof @@ -0,0 +1,16 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SqlServerDatabaseMail")] +class MSFT_SqlServerDatabaseMail : OMI_BaseResource +{ + [Key, Description("The name of the Database Mail account.")] String AccountName; + [Key, Description("The name of the SQL instance to be configured.")] String InstanceName; + [Required, Description("The e-mail address from which mail will originate.")] String EmailAddress; + [Required, Description("The fully qualified domain name of the mail server name to which e-mail are sent.")] String MailServerName; + [Required, Description("The profile name of the Database Mail.")] String ProfileName; + [Write, Description("Specifies the desired state of the Database Mail. When set to 'Present', the Database Mail will be created. When set to 'Absent', the Database Mail will be removed. Default value is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("The hostname of the SQL Server to be configured. Defaults to $env:COMPUTERNAME.")] String ServerName; + [Write, Description("The display name of the outgoing mail server. Default value is the same value assigned to parameter MailServerName.")] String DisplayName; + [Write, Description("The e-mail address to which the receiver of e-mails will reply to. Default value is the same e-mail address assigned to parameter EmailAddress.")] String ReplyToAddress; + [Write, Description("The description of the Database Mail.")] String Description; + [Write, Description("The logging level that the Database Mail will use. If not specified the default logging level is 'Extended'."), ValueMap{"Normal","Extended","Verbose"}, Values{"Normal","Extended","Verbose"}] String LoggingLevel; + [Write, Description("The TCP port used for communication. Default value is port 25.")] UInt16 TcpPort; +}; diff --git a/DSCResources/MSFT_SqlServerDatabaseMail/en-US/MSFT_SqlServerDatabaseMail.strings.psd1 b/DSCResources/MSFT_SqlServerDatabaseMail/en-US/MSFT_SqlServerDatabaseMail.strings.psd1 new file mode 100644 index 000000000..ec651d59f --- /dev/null +++ b/DSCResources/MSFT_SqlServerDatabaseMail/en-US/MSFT_SqlServerDatabaseMail.strings.psd1 @@ -0,0 +1,34 @@ +<# + Localized resources for MSFT_SqlServerDatabasemail + + Note: The named text strings MailServerProperty* are used in conjunction with + UpdatingPropertyOfMailServer to build a complete localized string. +#> + +ConvertFrom-StringData @' + ConnectToSqlInstance = Connecting to SQL Server instance '{0}\\{1}'. + DatabaseMailEnabled = SQL Server Database Mail is enabled. Database Mail XPs are set to {0}. + GetConfiguration = Account name '{0}' was found, returning the current state of the Database Mail configuration. + DatabaseMailDisabled = SQL Server Database Mail is disabled. Database Mail XPs are disabled. + AccountIsMissing = Account name '{0}' was not found. + EnablingDatabaseMail = Database Mail XPs are set to {0}. Configure the SQL Server instance '{1}\\{2}' to enable Database Mail. + ChangingLoggingLevel = Changing the SQL Server Database Mail logging level to '{0}' (value '{1}'). + CurrentLoggingLevel = SQL Server Database Mail logging level is '{0}' (value '{1}'). + CreatingMailAccount = Creating the mail account '{0}'. + MailAccountExist = Mail account '{0}' already exist. + UpdatingPropertyOfMailServer = Updating {2} of outgoing mail server. Current value is '{0}', expected '{1}'. + MailServerPropertyDisplayName = display name + MailServerPropertyDescription = description + MailServerPropertyEmailAddress = e-mail address + MailServerPropertyReplyToEmailAddress = reply to e-mail address + MailServerPropertyServerName = server name + MailServerPropertyTcpPort = TCP port + CreatingMailProfile = Creating a public default profile '{0}'. + MailProfileExist = The public default profile '{0}' already exist. + ConfigureSqlAgent = Configure the SQL Agent to use Database Mail. + SqlAgentAlreadyConfigured = The SQL Agent is already configured to use Database Mail. + TestingConfiguration = Determines if the Database Mail is in the desired state. + RemovingSqlAgentConfiguration = Configure the SQL Agent to not use Database Mail (changing it back to SQL Agent Mail). + RemovingMailProfile = Removing the public default profile '{0}'. + RemovingMailAccount = Removing the mail account '{0}'. +'@ diff --git a/Examples/README.md b/Examples/README.md index ddca0a02c..8b0049e2a 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -22,6 +22,7 @@ These are the links to the examples for each individual resource. - [SqlRS](/Examples/Resources/SqlRS) - [SqlScript](/Examples/Resources/SqlScript) - [SqlServerConfiguration](/Examples/Resources/SqlServerConfiguration) +- [SqlServerDatabaseMail](/Examples/Resources/SqlServerDatabaseMail) - [SqlServerEndpoint](/Examples/Resources/SqlServerEndpoint) - [SqlServerEndpointPermission](/Examples/Resources/SqlServerEndpointPermission) - [SqlServerEndpointState](/Examples/Resources/SqlServerEndpointState) diff --git a/Examples/Resources/SqlServerDatabaseMail/1-EnableDatabaseMail.ps1 b/Examples/Resources/SqlServerDatabaseMail/1-EnableDatabaseMail.ps1 new file mode 100644 index 000000000..9060eb3e0 --- /dev/null +++ b/Examples/Resources/SqlServerDatabaseMail/1-EnableDatabaseMail.ps1 @@ -0,0 +1,67 @@ +<# + .EXAMPLE + This example will enable Database Mail on a SQL Server instance. + +#> +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQL2016' + + MailServerName = 'mail.company.local' + AccountName = 'MyMail' + ProfileName = 'MyMailProfile' + EmailAddress = 'NoReply@company.local' + Description = 'Default mail account and profile.' + LoggingLevel = 'Normal' + TcpPort = 25 + + <# + NOTE! THIS IS NOT RECOMMENDED IN PRODUCTION. + This is added so that AppVeyor automatic tests can pass, otherwise + the tests will fail on passwords being in plain text and not being + encrypted. Because it is not possible to have a certificate in + AppVeyor to encrypt the passwords we need to add the parameter + 'PSDscAllowPlainTextPassword'. + NOTE! THIS IS NOT RECOMMENDED IN PRODUCTION. + #> + PSDscAllowPlainTextPassword = $true + } + ) +} + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + $SqlInstallCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost { + SqlServerDatabaseMail 'EnableDatabaseMail' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + AccountName = $Node.AccountName + ProfileName = $Node.ProfileName + EmailAddress = $Node.EmailAddress + ReplyToAddress = $Node.EmailAddress + DisplayName = $Node.MailServerName + MailServerName = $Node.MailServerName + Description = $Node.Description + LoggingLevel = $Node.LoggingLevel + TcpPort = $Node.TcpPort + + PsDscRunAsCredential = $SqlInstallCredential + } + } +} + diff --git a/Examples/Resources/SqlServerDatabaseMail/2-DisableDatabaseMail.ps1 b/Examples/Resources/SqlServerDatabaseMail/2-DisableDatabaseMail.ps1 new file mode 100644 index 000000000..c2928195c --- /dev/null +++ b/Examples/Resources/SqlServerDatabaseMail/2-DisableDatabaseMail.ps1 @@ -0,0 +1,61 @@ +<# + .EXAMPLE + This example will disable Database Mail on a SQL Server instance. + +#> +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQL2016' + + MailServerName = 'mail.company.local' + AccountName = 'MyMail' + ProfileName = 'MyMailProfile' + EmailAddress = 'NoReply@company.local' + Description = 'Default mail account and profile.' + LoggingLevel = 'Normal' + TcpPort = 25 + + <# + NOTE! THIS IS NOT RECOMMENDED IN PRODUCTION. + This is added so that AppVeyor automatic tests can pass, otherwise + the tests will fail on passwords being in plain text and not being + encrypted. Because it is not possible to have a certificate in + AppVeyor to encrypt the passwords we need to add the parameter + 'PSDscAllowPlainTextPassword'. + NOTE! THIS IS NOT RECOMMENDED IN PRODUCTION. + #> + PSDscAllowPlainTextPassword = $true + } + ) +} + +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + $SqlInstallCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost { + SqlServerDatabaseMail 'DisableDatabaseMail' + { + Ensure = 'Absent' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + AccountName = $Node.AccountName + ProfileName = $Node.ProfileName + EmailAddress = $Node.EmailAddress + MailServerName = $Node.MailServerName + + PsDscRunAsCredential = $SqlInstallCredential + } + } +} diff --git a/README.md b/README.md index b2f5881f9..354c50f5f 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ A full list of changes in each version can be found in the [change log](CHANGELO functionality to T-SQL. * [**SqlServerConfiguration**](#sqlserverconfiguration) resource to manage [SQL Server Configuration Options](https://msdn.microsoft.com/en-us/library/ms189631.aspx). +* [**SqlServerDatabaseMail**](#sqlserverdatabasemail) resource + to manage SQL Server Database Mail. * [**SqlServerEndpoint**](#sqlserverendpoint) resource to ensure database endpoint is present or absent. * [**SqlServerEndpointPermission**](#sqlserverendpointpermission) Grant or revoke @@ -830,6 +832,47 @@ No description. * [Configure two instances on the same server to have CLR enabled](/Examples/Resources/SqlServerConfiguration/1-ConfigureTwoInstancesOnTheSameServerToEnableClr.ps1) * [Configure a instance to have 'Priority Boost' enabled](/Examples/Resources/SqlServerConfiguration/2-ConfigureInstanceToEnablePriorityBoost.ps1) +### SqlServerDatabaseMail + +Resource to manage SQL Server Database Mail. + +#### 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 Agent. + +#### Parameters + +* **`[String]` AccountName** _(Key)_: The name of the Database Mail account. +* **`[String]` InstanceName** _(Key)_: Name of the SQL instance to be configured. +* **`[String]` EmailAddress** _(Required)_: The e-mail address from which mail + will originate. +* **`[String]` MailServerName** _(Required)_: The fully qualified domain name of + the mail server name to which e-mail are sent. +* **`[String]` ProfileName** _(Required)_: The profile name of the Database Mail. +* **`[String]` Ensure** _(Write)_: Specifies the desired state of the Database Mail. + When set to 'Present', the Database Mail will be created. When set to 'Absent', + the Database Mail will be removed. Default value is 'Present'. +* **`[String]` ServerName** _(Write)_: The hostname of the SQL Server to be configured. + Defaults to $env:COMPUTERNAME. +* **`[String]` DisplayName** _(Write)_: The display name of the outgoing mail server. + Default value is the same value assigned to parameter MailServerName. +* **`[String]` ReplyToAddress** _(Write)_: The e-mail address to which the receiver + of e-mails will reply to. Default value is the same e-mail address assigned to + parameter EmailAddress. +* **`[String]` Description** _(Write)_: The description of the Database Mail. +* **`[String]` LoggingLevel** _(Write)_: The logging level that the Database Mail + will use. If not specified the default logging level is 'Extended'. + { Normal | *Extended* | Verbose }. +* **`[UInt16]` TcpPort** _(Write)_: The TCP port used for communication. Default + value is port 25. + +#### Examples + +* [Enable Database Mail](/Examples/Resources/SqlServerDatabaseMail/1-EnableDatabaseMail.ps1) +* [Disable Database Mail](/Examples/Resources/SqlServerDatabaseMail/2-DisableDatabaseMail.ps1) + ### SqlServerEndpoint This resource is used to create an endpoint. Currently it only supports creating diff --git a/SqlServerDscHelper.psm1 b/SqlServerDscHelper.psm1 index bc8048448..52c4015ab 100644 --- a/SqlServerDscHelper.psm1 +++ b/SqlServerDscHelper.psm1 @@ -632,7 +632,8 @@ function Test-SQLDscParameterState { switch ($desiredType.Name) { - 'String' { + 'String' + { if (-not [System.String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` -not [System.String]::IsNullOrEmpty($DesiredValues.$fieldName)) { @@ -643,7 +644,8 @@ function Test-SQLDscParameterState } } - 'Int32' { + 'Int32' + { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) { @@ -654,7 +656,8 @@ function Test-SQLDscParameterState } } - 'Int16' { + { $_ -eq 'Int16' -or $_ -eq 'UInt16'} + { if (-not ($DesiredValues.$fieldName -eq 0) -or ` -not ($null -eq $CurrentValues.$fieldName)) { @@ -665,7 +668,8 @@ function Test-SQLDscParameterState } } - default { + default + { Write-Warning -Message ($script:localizedData.UnableToCompareProperty ` -f $fieldName, $desiredType.Name) diff --git a/Tests/Integration/MSFT_SqlServerDatabaseMail.Integration.Tests.ps1 b/Tests/Integration/MSFT_SqlServerDatabaseMail.Integration.Tests.ps1 new file mode 100644 index 000000000..9d6321511 --- /dev/null +++ b/Tests/Integration/MSFT_SqlServerDatabaseMail.Integration.Tests.ps1 @@ -0,0 +1,164 @@ +$script:DSCModuleName = 'SqlServerDsc' +$script:DSCResourceFriendlyName = 'SqlServerDatabaseMail' +$script:DSCResourceName = "MSFT_$($script:DSCResourceFriendlyName)" + +if (-not $env:APPVEYOR -eq $true) +{ + Write-Warning -Message ('Integration test for {0} will be skipped unless $env:APPVEYOR equals $true' -f $script:DSCResourceName) + return +} + +#region HEADER +# Integration Test Template Version: 1.1.2 +[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:DSCModuleName ` + -DSCResourceName $script:DSCResourceName ` + -TestType Integration + +#endregion + +$mockSqlInstallAccountPassword = ConvertTo-SecureString -String 'P@ssw0rd1' -AsPlainText -Force +$mockSqlInstallAccountUserName = "$env:COMPUTERNAME\SqlInstall" +$mockSqlInstallCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $mockSqlInstallAccountUserName, $mockSqlInstallAccountPassword + +try +{ + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName).config.ps1" + . $configFile + + $mockMailServerName = $ConfigurationData.AllNodes.MailServerName + $mockAccountName = $ConfigurationData.AllNodes.AccountName + $mockProfileName = $ConfigurationData.AllNodes.ProfileName + $mockEmailAddress = $ConfigurationData.AllNodes.EmailAddress + $mockDescription = $ConfigurationData.AllNodes.Description + $mockLoggingLevel = $ConfigurationData.AllNodes.LoggingLevel + $mockTcpPort = $ConfigurationData.AllNodes.TcpPort + + Describe "$($script:DSCResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:DSCResourceFriendlyName)]Integration_Test" + } + + $configurationName = "$($script:DSCResourceName)_Add_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + SqlInstallCredential = $mockSqlInstallCredential + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $currentConfiguration = Get-DscConfiguration + + $resourceCurrentState = $currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName + } | Where-Object -FilterScript { + $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.AccountName | Should -Be $mockAccountName + $resourceCurrentState.ProfileName | Should -Be $mockProfileName + $resourceCurrentState.EmailAddress | Should -Be $mockEmailAddress + $resourceCurrentState.ReplyToAddress | Should -Be $mockEmailAddress + $resourceCurrentState.DisplayName | Should -Be $mockMailServerName + $resourceCurrentState.MailServerName | Should -Be $mockMailServerName + $resourceCurrentState.Description | Should -Be $mockDescription + $resourceCurrentState.LoggingLevel | Should -Be $mockLoggingLevel + $resourceCurrentState.TcpPort | Should -Be $mockTcpPort + } + } + + $configurationName = "$($script:DSCResourceName)_Remove_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + SqlInstallCredential = $mockSqlInstallCredential + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $currentConfiguration = Get-DscConfiguration + + $resourceCurrentState = $currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName + } | Where-Object -FilterScript { + $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.AccountName | Should -BeNullOrEmpty + $resourceCurrentState.ProfileName | Should -BeNullOrEmpty + $resourceCurrentState.EmailAddress | Should -BeNullOrEmpty + $resourceCurrentState.ReplyToAddress | Should -BeNullOrEmpty + $resourceCurrentState.DisplayName | Should -BeNullOrEmpty + $resourceCurrentState.MailServerName | Should -BeNullOrEmpty + $resourceCurrentState.Description | Should -BeNullOrEmpty + $resourceCurrentState.LoggingLevel | Should -BeNullOrEmpty + $resourceCurrentState.TcpPort | Should -BeNullOrEmpty + } + } + } +} +finally +{ + #region FOOTER + + Restore-TestEnvironment -TestEnvironment $TestEnvironment + + #endregion +} diff --git a/Tests/Integration/MSFT_SqlServerDatabaseMail.config.ps1 b/Tests/Integration/MSFT_SqlServerDatabaseMail.config.ps1 new file mode 100644 index 000000000..6220ff418 --- /dev/null +++ b/Tests/Integration/MSFT_SqlServerDatabaseMail.config.ps1 @@ -0,0 +1,86 @@ +# This is used to make sure the integration test run in the correct order. +[Microsoft.DscResourceKit.IntegrationTest(OrderNumber = 2)] +param() + +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQL2016' + + PSDscAllowPlainTextPassword = $true + + MailServerName = 'mail.company.local' + AccountName = 'MyMail' + ProfileName = 'MyMailProfile' + EmailAddress = 'NoReply@company.local' + Description = 'Default mail account and profile.' + LoggingLevel = 'Normal' + TcpPort = 25 + } + ) +} + +Configuration MSFT_SqlServerDatabaseMail_Add_Config +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + $SqlInstallCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost { + SqlServerDatabaseMail 'Integration_Test' + { + Ensure = 'Present' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + AccountName = $Node.AccountName + ProfileName = $Node.ProfileName + EmailAddress = $Node.EmailAddress + ReplyToAddress = $Node.EmailAddress + DisplayName = $Node.MailServerName + MailServerName = $Node.MailServerName + Description = $Node.Description + LoggingLevel = $Node.LoggingLevel + TcpPort = $Node.TcpPort + + PsDscRunAsCredential = $SqlInstallCredential + } + } +} + +Configuration MSFT_SqlServerDatabaseMail_Remove_Config +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSCredential] + $SqlInstallCredential + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost { + SqlServerDatabaseMail 'Integration_Test' + { + Ensure = 'Absent' + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + AccountName = $Node.AccountName + ProfileName = $Node.ProfileName + EmailAddress = $Node.EmailAddress + MailServerName = $Node.MailServerName + + PsDscRunAsCredential = $SqlInstallCredential + } + } +} + + diff --git a/Tests/SqlServerDscCommon.Tests.ps1 b/Tests/SqlServerDscCommon.Tests.ps1 index 7303b6425..9fb391035 100644 --- a/Tests/SqlServerDscCommon.Tests.ps1 +++ b/Tests/SqlServerDscCommon.Tests.ps1 @@ -2,7 +2,7 @@ $script:moduleRoot = Split-Path $PSScriptRoot -Parent Describe 'SqlServerDsc module common tests' { Context -Name 'When the resource should be used to compile a configuration in Azure Automation' { - $fullPathHardLimit = 129 # 129 characters is the current maxmium for a relative path to be able to compile configurations in Azure Automation. + $fullPathHardLimit = 129 # 129 characters is the current maximum for a relative path to be able to compile configurations in Azure Automation. $allModuleFiles = Get-ChildItem -Path $script:moduleRoot -Recurse $testCaseModuleFile = @() diff --git a/Tests/Unit/MSFT_SqlServerDatabaseMail.Tests.ps1 b/Tests/Unit/MSFT_SqlServerDatabaseMail.Tests.ps1 new file mode 100644 index 000000000..f62347a0c --- /dev/null +++ b/Tests/Unit/MSFT_SqlServerDatabaseMail.Tests.ps1 @@ -0,0 +1,788 @@ +$script:DSCModuleName = 'SqlServerDsc' +$script:DSCResourceName = 'MSFT_SqlServerDatabaseMail' + +#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 { + $mockServerName = 'localhost' + $mockInstanceName = 'MSSQLSERVER' + $mockAccountName = 'MyMail' + $mockEmailAddress = 'NoReply@company.local' + $mockReplyToAddress = $mockEmailAddress + $mockProfileName = 'MyMailProfile' + $mockMailServerName = 'mail.company.local' + $mockDisplayName = $mockMailServerName + $mockDescription = 'My mail description' + $mockTcpPort = 25 + + $mockDatabaseMailDisabledConfigValue = 0 + $mockDatabaseMailEnabledConfigValue = 1 + + $mockAgentMailTypeDatabaseMail = 'DatabaseMail' + $mockAgentMailTypeSqlAgentMail = 'SQLAgentMail' + + $mockLoggingLevelNormal = 'Normal' + $mockLoggingLevelNormalValue = '1' + $mockLoggingLevelExtended = 'Extended' + $mockLoggingLevelExtendedValue = '2' + $mockLoggingLevelVerbose = 'Verbose' + $mockLoggingLevelVerboseValue = '3' + + $mockMissingAccountName = 'MissingAccount' + + # Default parameters that are used for the It-blocks. + $mockDefaultParameters = @{ + InstanceName = $mockInstanceName + ServerName = $mockServerName + AccountName = $mockAccountName + EmailAddress = $mockEmailAddress + MailServerName = $mockMailServerName + ProfileName = $mockProfileName + } + + # Contains mocked object that is used between several mocks. + $mailAccountObject = { + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockAccountName -PassThru | + Add-Member -MemberType NoteProperty -Name 'DisplayName' -Value $mockDisplayName -PassThru | + Add-Member -MemberType NoteProperty -Name 'EmailAddress' -Value $mockEmailAddress -PassThru | + Add-Member -MemberType NoteProperty -Name 'ReplyToAddress' -Value $mockReplyToAddress -PassThru | + Add-Member -MemberType NoteProperty -Name 'Description' -Value $mockDynamicDescription -PassThru | + Add-Member -MemberType ScriptProperty -Name 'MailServers' -Value { + return @( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockMailServerName -PassThru | + Add-Member -MemberType NoteProperty -Name 'Port' -Value $mockTcpPort -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Rename' -Value { + $script:MailServerRenameMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:MailServerAlterMethodCallCount += 1 + } -PassThru -Force + ) + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Create' -Value { + $script:MailAccountCreateMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Drop' -Value { + $script:MailAccountDropMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:MailAccountAlterMethodCallCount += 1 + } -PassThru -Force + } + + $mockNewObject_MailAccount = { + # This executes the variable that contains the mock + return @( & $mailAccountObject ) + } + + $mailProfileObject = { + return @( + New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Name' -Value $mockProfileName -PassThru | + Add-Member -MemberType NoteProperty -Name 'Description' -Value $mockProfileName -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Create' -Value { + $script:MailProfileCreateMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:MailProfileAlterMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Drop' -Value { + $script:MailProfileDropMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'AddPrincipal' -Value { + $script:MailProfileAddPrincipalMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'AddAccount' -Value { + $script:MailProfileAddAccountMethodCallCount += 1 + } -PassThru -Force + ) + } + + $mockNewObject_MailProfile = { + # This executes the variable that contains the mock + return @( & $mailProfileObject ) + } + + $mockConnectSQL = { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'Configuration' -Value { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'DatabaseMailEnabled' -Value { + return New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'RunValue' -Value $mockDynamicDatabaseMailEnabledRunValue -PassThru | + Add-Member -MemberType NoteProperty -Name 'ConfigValue' -Value $mockDatabaseMailDisabledConfigValue -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:ConfigurationAlterMethodCallCount += 1 + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptProperty -Name 'Mail' -Value { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'Accounts' -Value { + # This executes the variable that contains the mock + return @( & $mailAccountObject ) + } -PassThru | + Add-Member -MemberType ScriptProperty -Name 'ConfigurationValues' -Value { + return @{ + 'LoggingLevel' = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Value' -Value $mockDynamicLoggingLevelValue -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:LoggingLevelAlterMethodCallCount += 1 + } -PassThru -Force + } + } -PassThru | + Add-Member -MemberType ScriptProperty -Name 'Profiles' -Value { + # This executes the variable that contains the mock + return @( & $mailProfileObject ) + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptProperty -Name 'JobServer' -Value { + return New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'AgentMailType' -Value $mockDynamicAgentMailType -PassThru | + Add-Member -MemberType NoteProperty -Name 'DatabaseMailProfile' -Value $mockDynamicDatabaseMailProfile -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + $script:JobServerAlterMethodCallCount += 1 + } -PassThru -Force + } -PassThru -Force + } + + Describe "MSFT_SqlServerDatabaseMail\Get-TargetResource" -Tag 'Get' { + BeforeAll { + $mockDynamicDatabaseMailEnabledRunValue = $mockDatabaseMailEnabledConfigValue + $mockDynamicLoggingLevelValue = $mockLoggingLevelExtendedValue + $mockDynamicDescription = $mockDescription + $mockDynamicAgentMailType = $mockAgentMailTypeDatabaseMail + $mockDynamicDatabaseMailProfile = $mockProfileName + } + + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $getTargetResourceParameters = $mockDefaultParameters.Clone() + } + + Context 'When the system is in the desired state' { + Context 'When the configuration is absent' { + BeforeEach { + $getTargetResourceParameters['AccountName'] = $mockMissingAccountName + } + + It 'Should return the state as absent' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.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 @getTargetResourceParameters + $result.ServerName | Should -Be $getTargetResourceParameters.ServerName + $result.InstanceName | Should -Be $getTargetResourceParameters.InstanceName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return $null for the rest of the properties' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.AccountName | Should -BeNullOrEmpty + $getTargetResourceResult.EmailAddress | Should -BeNullOrEmpty + $getTargetResourceResult.MailServerName | Should -BeNullOrEmpty + $getTargetResourceResult.LoggingLevel | Should -BeNullOrEmpty + $getTargetResourceResult.ProfileName | Should -BeNullOrEmpty + $getTargetResourceResult.DisplayName | Should -BeNullOrEmpty + $getTargetResourceResult.ReplyToAddress | Should -BeNullOrEmpty + $getTargetResourceResult.Description | Should -BeNullOrEmpty + $getTargetResourceResult.TcpPort | Should -BeNullOrEmpty + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration is present' { + It 'Should return the state as present' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.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 @getTargetResourceParameters + $result.ServerName | Should -Be $getTargetResourceParameters.ServerName + $result.InstanceName | Should -Be $getTargetResourceParameters.InstanceName + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + + It 'Should return the correct values for the rest of the properties' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.AccountName | Should -Be $mockAccountName + $getTargetResourceResult.EmailAddress | Should -Be $mockEmailAddress + $getTargetResourceResult.MailServerName | Should -Be $mockMailServerName + $getTargetResourceResult.LoggingLevel | Should -Be $mockLoggingLevelExtended + $getTargetResourceResult.ProfileName | Should -Be $mockProfileName + $getTargetResourceResult.DisplayName | Should -Be $mockDisplayName + $getTargetResourceResult.ReplyToAddress | Should -Be $mockReplyToAddress + $getTargetResourceResult.Description | Should -Be $mockDescription + $getTargetResourceResult.TcpPort | Should -Be $mockTcpPort + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the current logging level is ''Normal''' { + BeforeAll { + $mockDynamicLoggingLevelValue = $mockLoggingLevelNormalValue + } + + It 'Should return the correct value for property LoggingLevel' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.LoggingLevel | Should -Be $mockLoggingLevelNormal + } + } + + Context 'When the current logging level is ''Extended''' { + BeforeAll { + $mockDynamicLoggingLevelValue = $mockLoggingLevelExtendedValue + } + + It 'Should return the correct value for property LoggingLevel' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.LoggingLevel | Should -Be $mockLoggingLevelExtended + } + } + + Context 'When the current logging level is ''Verbose''' { + BeforeAll { + $mockDynamicLoggingLevelValue = $mockLoggingLevelVerboseValue + } + + It 'Should return the correct value for property LoggingLevel' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.LoggingLevel | Should -Be $mockLoggingLevelVerbose + } + } + + Context 'When the current description is returned as an empty string' { + BeforeAll { + $mockDynamicDescription = '' + } + + It 'Should return $null for property Description' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.Description | Should -BeNullOrEmpty + } + } + + Context 'When the Database Mail feature is disabled' { + BeforeAll { + $mockDynamicDatabaseMailEnabledRunValue = $mockDatabaseMailDisabledConfigValue + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + $getTargetResourceResult.Ensure | Should -Be 'Absent' + $getTargetResourceResult.AccountName | Should -BeNullOrEmpty + } + } + } + + Assert-VerifiableMock + } + + Describe "MSFT_SqlServerDatabaseMail\Test-TargetResource" -Tag 'Test' { + BeforeAll { + $mockDynamicDatabaseMailEnabledRunValue = $mockDatabaseMailEnabledConfigValue + $mockDynamicLoggingLevelValue = $mockLoggingLevelExtendedValue + $mockDynamicDescription = $mockDescription + $mockDynamicAgentMailType = $mockAgentMailTypeDatabaseMail + $mockDynamicDatabaseMailProfile = $mockProfileName + } + + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + } + + Context 'When the system is in the desired state' { + Context 'When the configuration is absent' { + BeforeEach { + $testTargetResourceParameters['Ensure'] = 'Absent' + $testTargetResourceParameters['AccountName'] = $mockMissingAccountName + } + + It 'Should return the state as $true' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -Be $true + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration is present' { + BeforeEach { + $testTargetResourceParameters['DisplayName'] = $mockDisplayName + $testTargetResourceParameters['ReplyToAddress'] = $mockReplyToAddress + $testTargetResourceParameters['Description'] = $mockDescription + $testTargetResourceParameters['LoggingLevel'] = $mockLoggingLevelExtended + $testTargetResourceParameters['TcpPort'] = $mockTcpPort + } + + It 'Should return the state as $true' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | 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 configuration should be absent' { + BeforeEach { + $testTargetResourceParameters['Ensure'] = 'Absent' + } + + It 'Should return the state as $false' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration should be present' { + $defaultTestCase = @{ + AccountName = $mockAccountName + EmailAddress = $mockEmailAddress + MailServerName = $mockMailServerName + ProfileName = $mockProfileName + DisplayName = $mockDisplayName + ReplyToAddress = $mockReplyToAddress + Description = $mockDescription + LoggingLevel = $mockLoggingLevelExtended + TcpPort = $mockTcpPort + } + + $testCaseAccountNameIsMissing = $defaultTestCase.Clone() + $testCaseAccountNameIsMissing['TestName'] = 'AccountName is missing' + $testCaseAccountNameIsMissing['AccountName'] = 'MissingAccountName' + + $testCaseEmailAddressIsWrong = $defaultTestCase.Clone() + $testCaseEmailAddressIsWrong['TestName'] = 'EmailAddress is wrong' + $testCaseEmailAddressIsWrong['EmailAddress'] = 'wrong@email.address' + + $testCaseMailServerNameIsWrong = $defaultTestCase.Clone() + $testCaseMailServerNameIsWrong['TestName'] = 'MailServerName is wrong' + $testCaseMailServerNameIsWrong['MailServerName'] = 'smtp.contoso.com' + + $testCaseProfileNameIsWrong = $defaultTestCase.Clone() + $testCaseProfileNameIsWrong['TestName'] = 'ProfileName is wrong' + $testCaseProfileNameIsWrong['ProfileName'] = 'NewProfile' + + $testCaseDisplayNameIsWrong = $defaultTestCase.Clone() + $testCaseDisplayNameIsWrong['TestName'] = 'DisplayName is wrong' + $testCaseDisplayNameIsWrong['DisplayName'] = 'New display name' + + $testCaseReplyToAddressIsWrong = $defaultTestCase.Clone() + $testCaseReplyToAddressIsWrong['TestName'] = 'ReplyToAddress is wrong' + $testCaseReplyToAddressIsWrong['ReplyToAddress'] = 'new-reply@email.address' + + $testCaseDescriptionIsWrong = $defaultTestCase.Clone() + $testCaseDescriptionIsWrong['TestName'] = 'Description is wrong' + $testCaseDescriptionIsWrong['Description'] = 'New description' + + $testCaseLoggingLevelIsWrong = $defaultTestCase.Clone() + $testCaseLoggingLevelIsWrong['TestName'] = 'LoggingLevel is wrong' + $testCaseLoggingLevelIsWrong['LoggingLevel'] = $mockLoggingLevelNormal + + $testCaseTcpPortIsWrong = $defaultTestCase.Clone() + $testCaseTcpPortIsWrong['TestName'] = 'TcpPort is wrong' + $testCaseTcpPortIsWrong['TcpPort'] = 2525 + + $testCases = @( + $testCaseAccountNameIsMissing + $testCaseEmailAddressIsWrong + $testCaseMailServerNameIsWrong + $testCaseProfileNameIsWrong + $testCaseDisplayNameIsWrong + $testCaseReplyToAddressIsWrong + $testCaseDescriptionIsWrong + $testCaseLoggingLevelIsWrong + $testCaseTcpPortIsWrong + ) + + It 'Should return the state as $false when ' -TestCases $testCases { + param + ( + $AccountName, + $EmailAddress, + $MailServerName, + $ProfileName, + $DisplayName, + $ReplyToAddress, + $Description, + $LoggingLevel, + $TcpPort + ) + + $testTargetResourceParameters['AccountName'] = $AccountName + $testTargetResourceParameters['EmailAddress'] = $EmailAddress + $testTargetResourceParameters['MailServerName'] = $MailServerName + $testTargetResourceParameters['ProfileName'] = $ProfileName + $testTargetResourceParameters['DisplayName'] = $DisplayName + $testTargetResourceParameters['ReplyToAddress'] = $ReplyToAddress + $testTargetResourceParameters['Description'] = $Description + $testTargetResourceParameters['LoggingLevel'] = $LoggingLevel + $testTargetResourceParameters['TcpPort'] = $TcpPort + + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -Be $false + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + } + + Assert-VerifiableMock + } + + Describe "MSFT_SqlServerDatabaseMail\Set-TargetResource" -Tag 'Set' { + BeforeAll { + $mockDynamicDatabaseMailEnabledRunValue = $mockDatabaseMailEnabledConfigValue + $mockDynamicLoggingLevelValue = $mockLoggingLevelExtendedValue + $mockDynamicDescription = $mockDescription + $mockDynamicAgentMailType = $mockAgentMailTypeDatabaseMail + $mockDynamicDatabaseMailProfile = $mockProfileName + } + + BeforeEach { + Mock -CommandName Connect-SQL -MockWith $mockConnectSQL -Verifiable + Mock -CommandName New-Object -MockWith $mockNewObject_MailAccount -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.SMO.Mail.MailAccount' + } -Verifiable + + Mock -CommandName New-Object -MockWith $mockNewObject_MailProfile -ParameterFilter { + $TypeName -eq 'Microsoft.SqlServer.Management.SMO.Mail.MailProfile' + } -Verifiable + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + + $script:ConfigurationAlterMethodCallCount = 0 + $script:MailAccountCreateMethodCallCount = 0 + $script:MailServerRenameMethodCallCount = 0 + $script:MailServerAlterMethodCallCount = 0 + $script:MailAccountAlterMethodCallCount = 0 + $script:MailProfileCreateMethodCallCount = 0 + $script:MailProfileAlterMethodCallCount = 0 + $script:MailProfileAddPrincipalMethodCallCount = 0 + $script:MailProfileAddAccountMethodCallCount = 0 + $script:JobServerAlterMethodCallCount = 0 + $script:LoggingLevelAlterMethodCallCount = 0 + $script:MailProfileDropMethodCallCount = 0 + $script:MailAccountDropMethodCallCount = 0 + + $mockDynamicExpectedAccountName = $mockMissingAccountName + } + + Context 'When the system is in the desired state' { + Context 'When the configuration is absent' { + BeforeEach { + $setTargetResourceParameters['Ensure'] = 'Absent' + $setTargetResourceParameters['AccountName'] = $mockMissingAccountName + $setTargetResourceParameters['ProfileName'] = 'MissingProfile' + + $mockDynamicDatabaseMailEnabledRunValue = $mockDatabaseMailDisabledConfigValue + $mockDynamicAgentMailType = $mockAgentMailTypeSqlAgentMail + $mockDynamicDatabaseMailProfile = $null + } + + It 'Should call the correct methods without throwing' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + $script:ConfigurationAlterMethodCallCount | Should -Be 0 + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When the configuration is present' { + BeforeEach { + $setTargetResourceParameters['DisplayName'] = $mockDisplayName + $setTargetResourceParameters['ReplyToAddress'] = $mockReplyToAddress + $setTargetResourceParameters['Description'] = $mockDescription + $setTargetResourceParameters['LoggingLevel'] = $mockLoggingLevelExtended + $setTargetResourceParameters['TcpPort'] = $mockTcpPort + } + + It 'Should call the correct methods without throwing' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + $script:ConfigurationAlterMethodCallCount | Should -Be 0 + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the configuration should be absent' { + BeforeEach { + $setTargetResourceParameters['Ensure'] = 'Absent' + } + + It 'Should return the state as $false' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + $script:JobServerAlterMethodCallCount | Should -Be 1 + $script:MailProfileDropMethodCallCount | Should -Be 1 + $script:MailAccountDropMethodCallCount | Should -Be 1 + $script:ConfigurationAlterMethodCallCount | Should -Be 1 + } + } + + Context 'When the configuration should be present' { + Context 'When Database Mail XPs is enabled but fails evaluation' { + $mockDynamicDatabaseMailEnabledRunValue = $mockDatabaseMailDisabledConfigValue + + <# + Note: This test also tests the code that enables + Database Mail XPs. + Because of how the current code is written it is not + possible to verify that the code works without it + throwing (can't mock the property + $sqlServerObject.Configuration.DatabaseMailEnabled.RunValue + to be both 0 before running and then changing the + mock to 1 during testing to pass evaluation) + #> + It 'Should throw the correct error message' { + { + Set-TargetResource @setTargetResourceParameters + } | Should -Throw $script:localizedData.DatabaseMailDisabled + + $script:ConfigurationAlterMethodCallCount | Should -Be 1 + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When account name is missing' { + #$mockDynamicDatabaseMailEnabledRunValue = $mockDatabaseMailDisabledConfigValue + + It 'Should call the correct methods without throwing' { + $setTargetResourceParameters['AccountName'] = $mockMissingAccountName + $setTargetResourceParameters['DisplayName'] = $mockDisplayName + $setTargetResourceParameters['ReplyToAddress'] = $mockReplyToAddress + $setTargetResourceParameters['Description'] = $mockDescription + $setTargetResourceParameters['LoggingLevel'] = $mockLoggingLevelExtended + $setTargetResourceParameters['TcpPort'] = $mockTcpPort + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + $script:ConfigurationAlterMethodCallCount | Should -Be 0 + $script:MailAccountCreateMethodCallCount | Should -Be 1 + $script:MailServerRenameMethodCallCount | Should -Be 1 + $script:MailServerAlterMethodCallCount | Should -Be 1 + $script:MailAccountAlterMethodCallCount | Should -Be 0 + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + + Context 'When properties are not in desired state' { + $defaultTestCase = @{ + AccountName = $mockAccountName + EmailAddress = $mockEmailAddress + MailServerName = $mockMailServerName + ProfileName = $mockProfileName + DisplayName = $mockDisplayName + ReplyToAddress = $mockReplyToAddress + Description = $mockDescription + LoggingLevel = $mockLoggingLevelExtended + TcpPort = $mockTcpPort + } + + $testCaseEmailAddressIsWrong = $defaultTestCase.Clone() + $testCaseEmailAddressIsWrong['TestName'] = 'EmailAddress is wrong' + $testCaseEmailAddressIsWrong['EmailAddress'] = 'wrong@email.address' + + $testCaseMailServerNameIsWrong = $defaultTestCase.Clone() + $testCaseMailServerNameIsWrong['TestName'] = 'MailServerName is wrong' + $testCaseMailServerNameIsWrong['MailServerName'] = 'smtp.contoso.com' + + $testCaseProfileNameIsWrong = $defaultTestCase.Clone() + $testCaseProfileNameIsWrong['TestName'] = 'ProfileName is wrong' + $testCaseProfileNameIsWrong['ProfileName'] = 'NewProfile' + + $testCaseDisplayNameIsWrong = $defaultTestCase.Clone() + $testCaseDisplayNameIsWrong['TestName'] = 'DisplayName is wrong' + $testCaseDisplayNameIsWrong['DisplayName'] = 'New display name' + + $testCaseReplyToAddressIsWrong = $defaultTestCase.Clone() + $testCaseReplyToAddressIsWrong['TestName'] = 'ReplyToAddress is wrong' + $testCaseReplyToAddressIsWrong['ReplyToAddress'] = 'new-reply@email.address' + + $testCaseDescriptionIsWrong = $defaultTestCase.Clone() + $testCaseDescriptionIsWrong['TestName'] = 'Description is wrong' + $testCaseDescriptionIsWrong['Description'] = 'New description' + + $testCaseLoggingLevelIsWrong_Normal = $defaultTestCase.Clone() + $testCaseLoggingLevelIsWrong_Normal['TestName'] = 'LoggingLevel is wrong, should be ''Normal''' + $testCaseLoggingLevelIsWrong_Normal['LoggingLevel'] = $mockLoggingLevelNormal + + $testCaseLoggingLevelIsWrong_Verbose = $defaultTestCase.Clone() + $testCaseLoggingLevelIsWrong_Verbose['TestName'] = 'LoggingLevel is wrong, should be ''Verbose''' + $testCaseLoggingLevelIsWrong_Verbose['LoggingLevel'] = $mockLoggingLevelVerbose + + $testCaseTcpPortIsWrong = $defaultTestCase.Clone() + $testCaseTcpPortIsWrong['TestName'] = 'TcpPort is wrong' + $testCaseTcpPortIsWrong['TcpPort'] = 2525 + + $testCases = @( + $testCaseEmailAddressIsWrong + $testCaseMailServerNameIsWrong + $testCaseProfileNameIsWrong + $testCaseDisplayNameIsWrong + $testCaseReplyToAddressIsWrong + $testCaseDescriptionIsWrong + $testCaseLoggingLevelIsWrong_Normal + $testCaseLoggingLevelIsWrong_Verbose + $testCaseTcpPortIsWrong + ) + + It 'Should return the state as $false when ' -TestCases $testCases { + param + ( + $TestName, + $AccountName, + $EmailAddress, + $MailServerName, + $ProfileName, + $DisplayName, + $ReplyToAddress, + $Description, + $LoggingLevel, + $TcpPort + ) + + $setTargetResourceParameters['AccountName'] = $AccountName + $setTargetResourceParameters['EmailAddress'] = $EmailAddress + $setTargetResourceParameters['MailServerName'] = $MailServerName + $setTargetResourceParameters['ProfileName'] = $ProfileName + $setTargetResourceParameters['DisplayName'] = $DisplayName + $setTargetResourceParameters['ReplyToAddress'] = $ReplyToAddress + $setTargetResourceParameters['Description'] = $Description + $setTargetResourceParameters['LoggingLevel'] = $LoggingLevel + $setTargetResourceParameters['TcpPort'] = $TcpPort + + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:ConfigurationAlterMethodCallCount | Should -Be 0 + $script:MailAccountCreateMethodCallCount | Should -Be 0 + + if ($TestName -like '*MailServerName*') + { + $script:MailServerRenameMethodCallCount | Should -Be 1 + $script:MailServerAlterMethodCallCount | Should -Be 1 + $script:MailAccountAlterMethodCallCount | Should -Be 0 + $script:MailProfileCreateMethodCallCount | Should -Be 0 + $script:MailProfileAlterMethodCallCount | Should -Be 0 + $script:MailProfileAddPrincipalMethodCallCount | Should -Be 0 + $script:MailProfileAddAccountMethodCallCount | Should -Be 0 + $script:JobServerAlterMethodCallCount | Should -Be 0 + $script:LoggingLevelAlterMethodCallCount | Should -Be 0 + } + elseif ($TestName -like '*TcpPort*') + { + $script:MailServerRenameMethodCallCount | Should -Be 0 + $script:MailServerAlterMethodCallCount | Should -Be 1 + $script:MailAccountAlterMethodCallCount | Should -Be 0 + $script:MailProfileCreateMethodCallCount | Should -Be 0 + $script:MailProfileAlterMethodCallCount | Should -Be 0 + $script:MailProfileAddPrincipalMethodCallCount | Should -Be 0 + $script:MailProfileAddAccountMethodCallCount | Should -Be 0 + $script:JobServerAlterMethodCallCount | Should -Be 0 + $script:LoggingLevelAlterMethodCallCount | Should -Be 0 + } + elseif ($TestName -like '*ProfileName*') + { + $script:MailServerRenameMethodCallCount | Should -Be 0 + $script:MailServerAlterMethodCallCount | Should -Be 0 + $script:MailAccountAlterMethodCallCount | Should -Be 0 + $script:MailProfileCreateMethodCallCount | Should -Be 1 + $script:MailProfileAlterMethodCallCount | Should -Be 1 + $script:MailProfileAddPrincipalMethodCallCount | Should -Be 1 + $script:MailProfileAddAccountMethodCallCount | Should -Be 1 + $script:JobServerAlterMethodCallCount | Should -Be 1 + $script:LoggingLevelAlterMethodCallCount | Should -Be 0 + } + elseif ($TestName -like '*LoggingLevel*') + { + $script:MailServerRenameMethodCallCount | Should -Be 0 + $script:MailServerAlterMethodCallCount | Should -Be 0 + $script:MailAccountAlterMethodCallCount | Should -Be 0 + $script:MailProfileCreateMethodCallCount | Should -Be 0 + $script:MailProfileAlterMethodCallCount | Should -Be 0 + $script:MailProfileAddPrincipalMethodCallCount | Should -Be 0 + $script:MailProfileAddAccountMethodCallCount | Should -Be 0 + $script:JobServerAlterMethodCallCount | Should -Be 0 + $script:LoggingLevelAlterMethodCallCount | Should -Be 1 + } + else + { + $script:MailServerRenameMethodCallCount | Should -Be 0 + $script:MailServerAlterMethodCallCount | Should -Be 0 + $script:MailAccountAlterMethodCallCount | Should -Be 1 + $script:MailProfileCreateMethodCallCount | Should -Be 0 + $script:MailProfileAlterMethodCallCount | Should -Be 0 + $script:MailProfileAddPrincipalMethodCallCount | Should -Be 0 + $script:MailProfileAddAccountMethodCallCount | Should -Be 0 + $script:JobServerAlterMethodCallCount | Should -Be 0 + $script:LoggingLevelAlterMethodCallCount | Should -Be 0 + } + + Assert-MockCalled Connect-SQL -Exactly -Times 1 -Scope It + } + } + } + } + + Assert-VerifiableMock + } + } +} +finally +{ + Invoke-TestCleanup +} + diff --git a/Tests/Unit/SqlServerDSCHelper.Tests.ps1 b/Tests/Unit/SqlServerDSCHelper.Tests.ps1 index 073a6c457..04b1a73cd 100644 --- a/Tests/Unit/SqlServerDSCHelper.Tests.ps1 +++ b/Tests/Unit/SqlServerDSCHelper.Tests.ps1 @@ -1228,6 +1228,18 @@ InModuleScope $script:moduleName { Test-SQLDscParameterState @testParameters | Should -Be $false } + It 'Should return false when a value is different for [UInt16]' { + $mockCurrentValues = @{ Example = [System.UInt16]1 } + $mockDesiredValues = @{ Example = [System.UInt16]2 } + + $testParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + Test-SQLDscParameterState @testParameters | Should -Be $false + } + It 'Should return false when a value is missing' { $mockCurrentValues = @{ } $mockDesiredValues = @{ Example = 'test' }