diff --git a/CHANGELOG.md b/CHANGELOG.md index 703a93232e..1a25444d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Removed - -- SqlServerDsc - - BREAKING CHANGE: Removed resource _SqlServerNetwork_. The functionality - is now covered by the resources _SqlServerProtocol_ and _SqlServerProtocolTcpIp_. - ### Added - SqlServerDsc diff --git a/README.md b/README.md index 1fcc85f980..0342119c76 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ A full list of changes in each version can be found in the [change log](CHANGELO * [**SqlServerMaxDop**](#sqlservermaxdop) resource to manage MaxDegree of Parallelism for SQL Server. * [**SqlServerMemory**](#sqlservermemory) resource to manage Memory for SQL Server. +* [**SqlServerNetwork**](#sqlservernetwork) resource to manage SQL Server Network + Protocols. * [**SqlServerPermission**](#sqlserverpermission) Grant or revoke permission on the SQL Server. * [**SqlServerProtocol**](#sqlserverprotocol) resource manage the SQL Server @@ -1580,6 +1582,50 @@ SQL Max Memory = TotalPhysicalMemory - (NumOfSQLThreads\*ThreadStackSize) - All issues are not listed here, see [here for all open issues](https://github.com/dsccommunity/SqlServerDsc/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+SqlServerMemory). +### SqlServerNetwork + +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 + +* Target machine must be running Windows Server 2008 R2 or later. +* Target machine must be running SQL Server Database Engine 2008 or later. + +#### Parameters + +* **`[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]` ServerName** _(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. +* **`[Boolean]` TcpDynamicPort** _(Write)_: Specifies whether the SQL Server + instance should use a dynamic port. Value cannot be set to $true if TcpPort + is set to a non-empty string. +* **`[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 TcpDynamicPort to + $false. +* **`[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 + +* [Enable TCP/IP with static port and restart SQL Server](/source/Examples/Resources/SqlServerNetwork/1-EnableTcpIpWithStaticPort.ps1) +* [Enable TCP/IP with dynamic port](/source/Examples/Resources/SqlServerNetwork/2-EnableTcpIpWithDynamicPort.ps1) + +#### Known issues + +All issues are not listed here, see [here for all open issues](https://github.com/dsccommunity/SqlServerDsc/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+SqlServerNetwork). + ### SqlServerPermission This resource sets server permissions to a user (login). diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6e62822dba..1ef28823f8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -210,6 +210,7 @@ stages: 'tests/Integration/DSC_SqlSetup.Integration.Tests.ps1' # Group 2 'tests/Integration/DSC_SqlAgentAlert.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlServerNetwork.Integration.Tests.ps1' 'tests/Integration/DSC_SqlServerLogin.Integration.Tests.ps1' 'tests/Integration/DSC_SqlServerEndPoint.Integration.Tests.ps1' 'tests/Integration/DSC_SqlServerDatabaseMail.Integration.Tests.ps1' diff --git a/source/DSCResources/DSC_SqlServerNetwork/DSC_SqlServerNetwork.psm1 b/source/DSCResources/DSC_SqlServerNetwork/DSC_SqlServerNetwork.psm1 new file mode 100644 index 0000000000..f40b70c719 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerNetwork/DSC_SqlServerNetwork.psm1 @@ -0,0 +1,391 @@ +$script:sqlServerDscHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SqlServerDsc.Common' +$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' + +Import-Module -Name $script:sqlServerDscHelperModulePath +Import-Module -Name $script:resourceHelperModulePath + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' + +<# + .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)] + [System.String] + $InstanceName, + + # For now there is only support for the tcp protocol. + [Parameter(Mandatory = $true)] + [ValidateSet('Tcp')] + [System.String] + $ProtocolName + ) + + Import-SQLPSModule + + $managedComputerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer + + Write-Verbose -Message ($script:localizedData.GetNetworkProtocol -f $ProtocolName, $InstanceName) + $tcp = $managedComputerObject.ServerInstances[$InstanceName].ServerProtocols[$ProtocolName] + + Write-Verbose -Message $script:localizedData.ReadingNetworkProperties + $returnValue = @{ + InstanceName = $InstanceName + ProtocolName = $ProtocolName + IsEnabled = $tcp.IsEnabled + TcpDynamicPort = ($tcp.IPAddresses['IPAll'].IPAddressProperties['TcpDynamicPorts'].Value -ge 0) + TcpPort = $tcp.IPAddresses['IPAll'].IPAddressProperties['TcpPort'].Value + } + + $returnValue.Keys | ForEach-Object { + Write-Verbose -Message "$_ = $($returnValue[$_])" + } + + return $returnValue +} + +<# + .SYNOPSIS + Sets the SQL Server network properties. + + .PARAMETER ServerName + 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 TcpDynamicPort + Specifies whether the SQL Server instance should use a dynamic port. + Value cannot be set to $true if TcpPort is set to a non-empty string. + + .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 + TcpDynamicPort to 'False'. + + .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()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Tcp')] + [System.String] + $ProtocolName, + + [Parameter()] + [System.Boolean] + $IsEnabled, + + [Parameter()] + [System.Boolean] + $TcpDynamicPort, + + [Parameter()] + [System.String] + $TcpPort, + + [Parameter()] + [System.Boolean] + $RestartService = $false, + + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 + ) + + if ($TcpDynamicPort -and $TcpPort) + { + $errorMessage = $script:localizedData.ErrorDynamicAndStaticPortSpecified + New-InvalidOperationException -Message $errorMessage + } + + $getTargetResourceResult = Get-TargetResource -InstanceName $InstanceName -ProtocolName $ProtocolName + + $desiredState = @{ + InstanceName = $InstanceName + ProtocolName = $ProtocolName + IsEnabled = $IsEnabled + TcpDynamicPort = $TcpDynamicPort + TcpPort = $TcpPort + } + + $isRestartNeeded = $false + + # Get-TargetResource makes the necessary calls so the type ManagedComputer is available. + $managedComputerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer + + Write-Verbose -Message ($script:localizedData.GetNetworkProtocol -f $ProtocolName, $InstanceName) + $tcp = $managedComputerObject.ServerInstances[$InstanceName].ServerProtocols[$ProtocolName] + + Write-Verbose -Message ($script:localizedData.CheckingProperty -f 'IsEnabled') + if ($desiredState.IsEnabled -ine $getTargetResourceResult.IsEnabled) + { + Write-Verbose -Message ($script:localizedData.UpdatingProperty -f 'IsEnabled', $getTargetResourceResult.IsEnabled, $desiredState.IsEnabled) + $tcp.IsEnabled = $desiredState.IsEnabled + $tcp.Alter() + + $isRestartNeeded = $true + } + + Write-Verbose -Message ($script:localizedData.CheckingProperty -f 'TcpDynamicPort') + if ($desiredState.TcpDynamicPort -ne $getTargetResourceResult.TcpDynamicPort) + { + # Translates the current and desired state to a string for display + $dynamicPortDisplayValueTable = @{ + $true = 'enabled' + $false = 'disabled' + } + + # Translates the desired state to a valid value + $desiredDynamicPortValue = @{ + $true = '0' + $false = '' + } + + $fromTcpDynamicPortDisplayValue = $dynamicPortDisplayValueTable[$getTargetResourceResult.TcpDynamicPort] + $toTcpDynamicPortDisplayValue = $dynamicPortDisplayValueTable[$desiredState.TcpDynamicPort] + + Write-Verbose -Message ($script:localizedData.UpdatingProperty -f 'TcpDynamicPorts', $fromTcpDynamicPortDisplayValue, $toTcpDynamicPortDisplayValue) + $tcp.IPAddresses['IPAll'].IPAddressProperties['TcpDynamicPorts'].Value = $desiredDynamicPortValue[$desiredState.TcpDynamicPort] + $tcp.Alter() + + $isRestartNeeded = $true + } + + Write-Verbose -Message ($script:localizedData.CheckingProperty -f 'TcpPort') + if ($desiredState.TcpPort -ine $getTargetResourceResult.TcpPort) + { + $fromTcpPort = $getTargetResourceResult.TcpPort + if ($fromTcpPort -eq '') + { + $fromTcpPort = 'none' + } + + $toTcpPort = $desiredState.TcpPort + if ($toTcpPort -eq '') + { + $toTcpPort = 'none' + } + + Write-Verbose -Message ($script:localizedData.UpdatingProperty -f 'TcpPort', $fromTcpPort, $toTcpPort) + $tcp.IPAddresses['IPAll'].IPAddressProperties['TcpPort'].Value = $desiredState.TcpPort + $tcp.Alter() + + $isRestartNeeded = $true + } + + if ($RestartService -and $isRestartNeeded) + { + $restartSqlServiceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Timeout = $RestartTimeout + } + + if ($getTargetResourceResult.IsEnabled -eq $false -and $IsEnabled -eq $true) + { + <# + If the protocol was disabled and now being enabled, is not possible + to connect to the instance to evaluate if it is a clustered instance. + This is being tracked in issue #1174. + #> + $restartSqlServiceParameters['SkipClusterCheck'] = $true + } + + if ($PSBoundParameters.ContainsKey('IsEnabled') -and $IsEnabled -eq $false) + { + # If the protocol is disabled it is not possible to connect to the instance. + $restartSqlServiceParameters['SkipWaitForOnline'] = $true + } + + Restart-SqlService @restartSqlServiceParameters + } +} + +<# + .SYNOPSIS + Sets the SQL Server network properties. + + .PARAMETER ServerName + 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 TcpDynamicPort + Specifies whether the SQL Server instance should use a dynamic port. + Value cannot be set to $true if TcpPort is set to a non-empty string. + + .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 + TcpDynamicPort to 'False'. + + .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()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [ValidateSet('Tcp')] + [System.String] + $ProtocolName, + + [Parameter()] + [System.Boolean] + $IsEnabled, + + [Parameter()] + [System.Boolean] + $TcpDynamicPort, + + [Parameter()] + [System.String] + $TcpPort, + + [Parameter()] + [System.Boolean] + $RestartService = $false, + + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 + ) + + if ($TcpDynamicPort -and $TcpPort) + { + $errorMessage = $script:localizedData.ErrorDynamicAndStaticPortSpecified + New-InvalidOperationException -Message $errorMessage + } + + $getTargetResourceResult = Get-TargetResource -InstanceName $InstanceName -ProtocolName $ProtocolName + + Write-Verbose -Message $script:localizedData.CompareStates + + $isInDesiredState = $true + + if ($ProtocolName -ne $getTargetResourceResult.ProtocolName) + { + Write-Verbose -Message ($script:localizedData.ExpectedPropertyValue -f 'ProtocolName', $ProtocolName, $getTargetResourceResult.ProtocolName) + + $isInDesiredState = $false + } + + if ($PSBoundParameters.ContainsKey('IsEnabled')) + { + if ($IsEnabled -ne $getTargetResourceResult.IsEnabled) + { + $evaluateEnableOrDisable = @{ + $true = 'enabled' + $false = 'disabled' + } + + Write-Verbose -Message ($script:localizedData.ExpectedPropertyValue -f 'IsEnabled', $evaluateEnableOrDisable[$IsEnabled], $evaluateEnableOrDisable[$getTargetResourceResult.IsEnabled]) + + $isInDesiredState = $false + } + } + + if ($PSBoundParameters.ContainsKey('TcpDynamicPort')) + { + if ($TcpDynamicPort -and $getTargetResourceResult.TcpDynamicPort -eq $false) + { + Write-Verbose -Message ($script:localizedData.ExpectedPropertyValue -f 'TcpDynamicPort', $TcpDynamicPort, $getTargetResourceResult.TcpDynamicPort) + + $isInDesiredState = $false + } + } + + if ($PSBoundParameters.ContainsKey('TcpPort')) + { + if ($getTargetResourceResult.TcpPort -eq '') + { + Write-Verbose -Message ($script:localizedData.ExpectedPropertyValue -f 'TcpPort', $TcpPort, $getTargetResourceResult.TcpPort) + + $isInDesiredState = $false + } + elseif ($TcpPort -ne $getTargetResourceResult.TcpPort) + { + Write-Verbose -Message ($script:localizedData.ExpectedPropertyValue -f 'TcpPort', $TcpPort, $getTargetResourceResult.TcpPort) + + $isInDesiredState = $false + } + } + + if ($isInDesiredState) + { + Write-Verbose -Message ($script:localizedData.InDesiredState) + } + + return $isInDesiredState +} + +Export-ModuleMember -Function *-TargetResource diff --git a/source/DSCResources/DSC_SqlServerNetwork/DSC_SqlServerNetwork.schema.mof b/source/DSCResources/DSC_SqlServerNetwork/DSC_SqlServerNetwork.schema.mof new file mode 100644 index 0000000000..2ab111a917 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerNetwork/DSC_SqlServerNetwork.schema.mof @@ -0,0 +1,12 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SqlServerNetwork")] +class DSC_SqlServerNetwork : OMI_BaseResource +{ + [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 ServerName; + [Write, Description("Enables or disables the network protocol.")] Boolean IsEnabled; + [Write, Description("Specifies whether the SQL Server instance should use a dynamic port. Value cannot be set to 'True' if TcpPort is set to a non-empty string.")] Boolean TcpDynamicPort; + [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 'False'.")] 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/source/DSCResources/DSC_SqlServerNetwork/en-US/DSC_SqlServerNetwork.strings.psd1 b/source/DSCResources/DSC_SqlServerNetwork/en-US/DSC_SqlServerNetwork.strings.psd1 new file mode 100644 index 0000000000..2454c12de8 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerNetwork/en-US/DSC_SqlServerNetwork.strings.psd1 @@ -0,0 +1,12 @@ +# Localized resources for SqlServerNetwork + +ConvertFrom-StringData @' + GetNetworkProtocol = Getting network protocol [{0}] for SQL instance [{1}]. + ReadingNetworkProperties = Reading current network properties. + CheckingProperty = Checking [{0}] property. + UpdatingProperty = Updating property [{0}] from [{1}] to [{2}]. + ExpectedPropertyValue = Expected property [{0}] value to be [{1}] but was [{2}]. + CompareStates = Comparing desired state with current state. + InDesiredState = System is in the desired state. + ErrorDynamicAndStaticPortSpecified = Unable to set both tcp dynamic port and tcp static port. Only one can be set. +'@ diff --git a/source/Examples/README.md b/source/Examples/README.md index 24ac35519c..9e52258772 100644 --- a/source/Examples/README.md +++ b/source/Examples/README.md @@ -34,6 +34,7 @@ These are the links to the examples for each individual resource. - [SqlServerLogin](Resources/SqlServerLogin) - [SqlServerMaxDop](Resources/SqlServerMaxDop) - [SqlServerMemory](Resources/SqlServerMemory) +- [SqlServerNetwork](Resources/SqlServerNetwork) - [SqlServerPermission](Resources/SqlServerPermission) - [SqlServerReplication](Resources/SqlServerReplication) - [SqlServerRole](Resources/SqlServerRole) diff --git a/source/Examples/Resources/SqlServerNetwork/1-EnableTcpIpWithStaticPort.ps1 b/source/Examples/Resources/SqlServerNetwork/1-EnableTcpIpWithStaticPort.ps1 new file mode 100644 index 0000000000..f2aef7e600 --- /dev/null +++ b/source/Examples/Resources/SqlServerNetwork/1-EnableTcpIpWithStaticPort.ps1 @@ -0,0 +1,32 @@ +<# + .DESCRIPTION + 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. + The resource will be run as the account provided in $SystemAdministratorAccount. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SystemAdministratorAccount + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlServerNetwork 'ChangeTcpIpOnDefaultInstance' + { + InstanceName = 'MSSQLSERVER' + ProtocolName = 'Tcp' + IsEnabled = $true + TCPDynamicPort = $false + TCPPort = 4509 + RestartService = $true + + PsDscRunAsCredential = $SystemAdministratorAccount + } + } +} diff --git a/source/Examples/Resources/SqlServerNetwork/2-EnableTcpIpWithDynamicPort.ps1 b/source/Examples/Resources/SqlServerNetwork/2-EnableTcpIpWithDynamicPort.ps1 new file mode 100644 index 0000000000..b9c14e829e --- /dev/null +++ b/source/Examples/Resources/SqlServerNetwork/2-EnableTcpIpWithDynamicPort.ps1 @@ -0,0 +1,31 @@ +<# + .DESCRIPTION + 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. + The resource will be run as the account provided in $SystemAdministratorAccount. +#> +Configuration Example +{ + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $SystemAdministratorAccount + ) + + Import-DscResource -ModuleName 'SqlServerDsc' + + node localhost + { + SqlServerNetwork 'ChangeTcpIpOnDefaultInstance' + { + InstanceName = 'MSSQLSERVER' + ProtocolName = 'Tcp' + IsEnabled = $true + TCPDynamicPort = $true + RestartService = $true + + PsDscRunAsCredential = $SystemAdministratorAccount + } + } +} diff --git a/tests/Integration/DSC_SqlServerNetwork.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerNetwork.Integration.Tests.ps1 new file mode 100644 index 0000000000..8356820565 --- /dev/null +++ b/tests/Integration/DSC_SqlServerNetwork.Integration.Tests.ps1 @@ -0,0 +1,143 @@ +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (-not (Test-BuildCategory -Type 'Integration' -Category @('Integration_SQL2016','Integration_SQL2017'))) +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceFriendlyName = 'SqlServerNetwork' +$script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + +try +{ + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' +} +catch [System.IO.FileNotFoundException] +{ + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' +} + +$script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' + +$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 + + Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $configurationName = "$($script:dscResourceName)_SetDisabled_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + + $resourceCurrentState.IsEnabled | Should -Be $ConfigurationData.AllNodes.Disabled + $resourceCurrentState.ProtocolName | Should -Be $ConfigurationData.AllNodes.ProtocolName + $resourceCurrentState.TcpDynamicPort | Should -Be $ConfigurationData.AllNodes.TcpDynamicPort + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + $configurationName = "$($script:dscResourceName)_SetEnabled_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + + $resourceCurrentState.IsEnabled | Should -Be $ConfigurationData.AllNodes.Enabled + $resourceCurrentState.ProtocolName | Should -Be $ConfigurationData.AllNodes.ProtocolName + $resourceCurrentState.TcpDynamicPort | Should -Be $ConfigurationData.AllNodes.TcpDynamicPort + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + } +} +finally +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} diff --git a/tests/Integration/DSC_SqlServerNetwork.config.ps1 b/tests/Integration/DSC_SqlServerNetwork.config.ps1 new file mode 100644 index 0000000000..ab035017cf --- /dev/null +++ b/tests/Integration/DSC_SqlServerNetwork.config.ps1 @@ -0,0 +1,88 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, + for real testing scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + + UserName = "$env:COMPUTERNAME\SqlInstall" + Password = 'P@ssw0rd1' + + ServerName = $env:COMPUTERNAME + InstanceName = 'DSCSQLTEST' + + ProtocolName = 'Tcp' + Enabled = $true + Disabled = $false + TcpDynamicPort = $true + RestartService = $true + + CertificateFile = $env:DscPublicCertificatePath + } + ) + } +} + +<# + .SYNOPSIS + Disable network protocol. +#> +Configuration DSC_SqlServerNetwork_SetDisabled_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerNetwork 'Integration_Test' + { + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + ProtocolName = $Node.ProtocolName + IsEnabled = $Node.Disabled + TcpDynamicPort = $Node.TcpDynamicPort + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} + +<# + .SYNOPSIS + Enable network protocol. +#> +Configuration DSC_SqlServerNetwork_SetEnabled_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerNetwork 'Integration_Test' + { + ServerName = $Node.ServerName + InstanceName = $Node.InstanceName + ProtocolName = $Node.ProtocolName + IsEnabled = $Node.Enabled + TcpDynamicPort = $Node.TcpDynamicPort + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} + diff --git a/tests/Unit/DSC_SqlServerNetwork.Tests.ps1 b/tests/Unit/DSC_SqlServerNetwork.Tests.ps1 new file mode 100644 index 0000000000..767621e55a --- /dev/null +++ b/tests/Unit/DSC_SqlServerNetwork.Tests.ps1 @@ -0,0 +1,540 @@ +<# + .SYNOPSIS + Automated unit test for DSC_SqlServerNetwork DSC resource. + + .NOTES + To run this script locally, please make sure to first run the bootstrap + script. Read more at + https://github.com/PowerShell/SqlServerDsc/blob/dev/CONTRIBUTING.md#bootstrap-script-assert-testenvironment +#> + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '..\TestHelpers\CommonTestHelper.psm1') + +if (-not (Test-BuildCategory -Type 'Unit')) +{ + return +} + +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceName = 'DSC_SqlServerNetwork' + +function Invoke-TestSetup +{ + try + { + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Invoke-TestSetup + +try +{ + InModuleScope $script:dscResourceName { + $mockInstanceName = 'TEST' + $mockTcpProtocolName = 'Tcp' + $mockNamedPipesProtocolName = 'NP' + $mockTcpDynamicPortNumber = '24680' + + $script:WasMethodAlterCalled = $false + + $mockFunction_NewObject_ManagedComputer = { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'ServerInstances' -Value { + return @{ + $mockInstanceName = New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'ServerProtocols' -Value { + return @{ + $mockDynamicValue_TcpProtocolName = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'IsEnabled' -Value $mockDynamicValue_IsEnabled -PassThru | + Add-Member -MemberType ScriptProperty -Name 'IPAddresses' -Value { + return @{ + 'IPAll' = New-Object -TypeName Object | + Add-Member -MemberType ScriptProperty -Name 'IPAddressProperties' -Value { + return @{ + 'TcpDynamicPorts' = New-Object -TypeName Object | + Add-Member -MemberType NoteProperty -Name 'Value' -Value $mockDynamicValue_TcpDynamicPort -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' -Value { + <# + 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' + } + + $mockDefaultParameters = @{ + InstanceName = $mockInstanceName + ProtocolName = $mockTcpProtocolName + } + + Describe "DSC_SqlServerNetwork\Get-TargetResource" -Tag 'Get'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Import-SQLPSModule + 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_TcpDynamicPort = '' + $mockDynamicValue_TcpPort = '4509' + } + + It 'Should return the correct values' { + $result = Get-TargetResource @testParameters + $result.IsEnabled | Should -Be $mockDynamicValue_IsEnabled + $result.TcpDynamicPort | Should -Be $false + $result.TcpPort | Should -Be $mockDynamicValue_TcpPort + + 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-VerifiableMock + } + + Describe "DSC_SqlServerNetwork\Test-TargetResource" -Tag 'Test'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Import-SQLPSModule + 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_TcpDynamicPort = '' + $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 + TcpDynamicPort = $false + 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 + TcpDynamicPort = $false + TcpPort = '4509' + } + } + + It 'Should return $false' { + $result = Test-TargetResource @testParameters + $result | Should -Be $false + } + } + + Context 'When ProtocolName is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $false + TcpDynamicPort = $false + TcpPort = '4509' + } + + # Not supporting any other than 'TCP' yet. + $testParameters['ProtocolName'] = 'Unknown' + } + + # Skipped since no other protocol is supported yet (issue #14). + It 'Should return $false' -Skip:$true { + $result = Test-TargetResource @testParameters + $result | Should -Be $false + } + } + + Context 'When current state is using static tcp port' { + Context 'When TcpDynamicPort is not in desired state' { + BeforeEach { + $testParameters += @{ + TcpDynamicPort = $true + 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_TcpDynamicPort = $mockTcpDynamicPortNumber + $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 TcpDynamicPort and TcpPort are being set' { + BeforeEach { + $testParameters += @{ + TcpDynamicPort = $true + TcpPort = '1433' + IsEnabled = $false + } + } + + It 'Should throw the correct error message' { + $testErrorMessage = $script:localizedData.ErrorDynamicAndStaticPortSpecified + { Test-TargetResource @testParameters } | Should -Throw $testErrorMessage + } + } + } + + Context 'When the system is in the desired state' { + BeforeEach { + $mockDynamicValue_TcpProtocolName = $mockTcpProtocolName + $mockDynamicValue_IsEnabled = $true + $mockDynamicValue_TcpDynamicPort = $mockTcpDynamicPortNumber + $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 TcpDynamicPort is in desired state' { + BeforeEach { + $testParameters += @{ + TcpDynamicPort = $true + IsEnabled = $true + } + } + + It 'Should return $true' { + $result = Test-TargetResource @testParameters + $result | Should -Be $true + } + } + } + + Assert-VerifiableMock + } + + Describe "DSC_SqlServerNetwork\Set-TargetResource" -Tag 'Set'{ + BeforeEach { + $testParameters = $mockDefaultParameters.Clone() + + Mock -CommandName Restart-SqlService -Verifiable + Mock -CommandName Import-SQLPSModule + 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_TcpDynamicPort = '' + $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' { + Context 'When IsEnabled should be $false' { + BeforeEach { + $testParameters += @{ + IsEnabled = $false + TcpDynamicPort = $false + 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 IsEnabled should be $true' { + BeforeEach { + $testParameters += @{ + IsEnabled = $true + TcpDynamicPort = $false + TcpPort = '4509' + RestartService = $true + } + + $mockExpectedValue_IsEnabled = $true + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + ProtocolName = $mockTcpProtocolName + IsEnabled = $false + TcpDynamicPort = $testParameters.TcpDynamicPort + TcpPort = $testParameters.TcpPort + } + } + } + + 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 -ParameterFilter { + $SkipClusterCheck -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When current state is using static tcp port' { + Context 'When TcpDynamicPort is not in desired state' { + BeforeEach { + $testParameters += @{ + IsEnabled = $true + TcpDynamicPort = $true + } + } + + 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 + TcpDynamicPort = $false + 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_TcpDynamicPort = $mockTcpDynamicPortNumber + $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 + TcpDynamicPort = $false + 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 TcpDynamicPort and TcpPort are being set' { + BeforeEach { + $testParameters += @{ + TcpDynamicPort = $true + TcpPort = '1433' + IsEnabled = $false + } + } + + It 'Should throw the correct error message' { + $testErrorMessage = ($script:localizedData.ErrorDynamicAndStaticPortSpecified) + { Set-TargetResource @testParameters } | Should -Throw $testErrorMessage + } + } + } + + 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_TcpDynamicPort = '' + $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 + TcpDynamicPort = $false + 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-VerifiableMock + } + } +} +finally +{ + Invoke-TestCleanup +} +