From 83760fe378452b82c60ec092fd40c69de78ca7ba Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 5 May 2020 11:04:26 +0200 Subject: [PATCH] SqlServerProtocolTcpIp: New resource proposal (#1517) - SqlServerDsc - Added new resource SqlServerProtocolTcpIp (issue #1378). - Changed to use the property `NuGetVersionV2` from GitVersion in the CI pipeline. --- CHANGELOG.md | 3 + README.md | 71 +- azure-pipelines.yml | 6 +- .../DSC_SqlServerProtocol.psm1 | 113 -- .../DSC_SqlServerProtocol/README.md | 6 + .../DSC_SqlServerProtocolTcpIp.psm1 | 736 ++++++++++ .../DSC_SqlServerProtocolTcpIp.schema.mof | 16 + .../DSC_SqlServerProtocolTcpIp/README.md | 22 + .../DSC_SqlServerProtocolTcpIp.strings.psd1 | 17 + ...gureIPAddressGroupIPAllWithDynamicPort.ps1 | 29 + ...igureIPAddressGroupIPAllWithStaticPort.ps1 | 29 + .../3-ConfigureIPAddressGroupIP1.ps1 | 41 + .../SqlServerDsc.Common.psd1 | 2 + .../SqlServerDsc.Common.psm1 | 112 ++ source/SqlServerDsc.psd1 | 1 + ...lServerProtocolTcpIp.Integration.Tests.ps1 | 89 ++ .../DSC_SqlServerProtocolTcpIp.config.ps1 | 76 + tests/Unit/DSC_SqlServerProtocol.Tests.ps1 | 82 -- .../Unit/DSC_SqlServerProtocolTcpIp.Tests.ps1 | 1273 +++++++++++++++++ tests/Unit/SqlServerDsc.Common.Tests.ps1 | 82 ++ 20 files changed, 2609 insertions(+), 197 deletions(-) create mode 100644 source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.psm1 create mode 100644 source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.schema.mof create mode 100644 source/DSCResources/DSC_SqlServerProtocolTcpIp/README.md create mode 100644 source/DSCResources/DSC_SqlServerProtocolTcpIp/en-US/DSC_SqlServerProtocolTcpIp.strings.psd1 create mode 100644 source/Examples/Resources/SqlServerProtocolTcpIp/1-ConfigureIPAddressGroupIPAllWithDynamicPort.ps1 create mode 100644 source/Examples/Resources/SqlServerProtocolTcpIp/2-ConfigureIPAddressGroupIPAllWithStaticPort.ps1 create mode 100644 source/Examples/Resources/SqlServerProtocolTcpIp/3-ConfigureIPAddressGroupIP1.ps1 create mode 100644 tests/Integration/DSC_SqlServerProtocolTcpIp.Integration.Tests.ps1 create mode 100644 tests/Integration/DSC_SqlServerProtocolTcpIp.config.ps1 create mode 100644 tests/Unit/DSC_SqlServerProtocolTcpIp.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 72e85f784..a268281a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) - SqlServerDsc - Added new resource SqlServerProtocol ([issue #1377](https://github.com/dsccommunity/SqlServerDsc/issues/1377)). + - Added new resource SqlServerProtocolTcpIp ([issue #1378](https://github.com/dsccommunity/SqlServerDsc/issues/1378)). - SqlSetup - A read only property `IsClustered` was added that can be used to determine if the instance is clustered. @@ -33,6 +34,8 @@ For older change log history see the [historic changelog](HISTORIC_CHANGELOG.md) marked as stale if the PR is not merged for 30 days (for example it is dependent on something else) ([issue #1504](https://github.com/dsccommunity/SqlServerDsc/issues/1504)). - Updated the CI pipeline to use latest version of the module ModuleBuilder. + - Changed to use the property `NuGetVersionV2` from GitVersion in the + CI pipeline. - SqlAlwaysOnService - BREAKING CHANGE: The parameter `ServerName` is now non-mandatory and defaults to `$env:COMPUTERNAME` ([issue #319](https://github.com/dsccommunity/SqlServerDsc/issues/319)). diff --git a/README.md b/README.md index 75b73f599..419c00a98 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,8 @@ A full list of changes in each version can be found in the [change log](CHANGELO the SQL Server. * [**SqlServerProtocol**](#sqlserverprotocol) resource manage the SQL Server protocols for a SQL Server instance. +* [**SqlServerProtocolTcpIp**](#sqlserverprotocoltcpip) resource manage the TCP/IP + protocol IP address groups for a SQL Server instance. * [**SqlServerReplication**](#sqlserverreplication) resource to manage SQL Replication distribution and publishing. * [**SqlServerRole**](#sqlserverrole) resource to manage SQL server roles. @@ -1693,7 +1695,7 @@ for a SQL Server instance. * **`[UInt16]` KeepAlive** _(Write)_: Specifies the keep alive duration in milliseconds. Only used for the TCP/IP protocol, ignored for all other protocols. -* **`[String` PipeName** _(Write)_: Specifies the name of the named pipe. +* **`[String]` PipeName** _(Write)_: Specifies the name of the named pipe. Only used for the Named Pipes protocol, ignored for all other protocols. * **`[Boolean]` SuppressRestart** _(Write)_: If set to $true then the any attempt by the resource to restart the service is suppressed. The default @@ -1719,6 +1721,73 @@ for a SQL Server instance. 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+SqlServerProtocol). +### SqlServerProtocolTcpIp + +The `SqlServerProtocolTcpIp` DSC resource manage the TCP/IP protocol IP +address groups for a SQL Server instance. + +#### Requirements + +* Target machine must be running Windows Server 2012 or later. +* Target machine must be running SQL Server Database Engine 2012 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer + PowerShell module. +* To configure a single IP address to listen on multiple ports, the + TcpIp protocol must also set the **Listen All** property to **No**. + This can be done with the resource `SqlServerProtocol` using the + parameter `ListenOnAllIpAddresses`. + +#### Parameters + +* **`[String]` InstanceName** _(Key)_: Specifies the name of the SQL Server + instance to enable the protocol for. +* **`[String]` IpAddressGroup** _(Key)_: Specifies the name of the IP address + group in the TCP/IP protocol, e.g. 'IP1', 'IP2' etc., or 'IPAll'. +* **`[String]` ServerName** _(Write)_: Specifies the host name of the SQL + Server to be configured. If the SQL Server belongs to a cluster or + availability group specify the host name for the listener or cluster group. + Default value is `$env:COMPUTERNAME`. +* **`[Boolean]` Enabled** _(Write)_: Specified if the IP address group should + be enabled or disabled. Only used if the IP address group is not set to + 'IPAll'. If not specified, the existing value will not be changed. +* **`[String]` IpAddress** _(Write)_: Specifies the IP address for the IP + adress group. Only used if the IP address group is not set to 'IPAll'. If + not specified, the existing value will not be changed. +* **`[Boolean]` UseTcpDynamicPort** _(Write)_: Specifies whether the SQL Server + instance should use a dynamic port. If not specified, the existing value + will not be changed. This parameter is not allowed to be used at the same + time as the parameter TcpPort. +* **`[String]` TcpPort** _(Write)_: Specifies 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 as a string value with the port numbers separated with a comma, + e.g. '1433,1500,1501'. This parameter is limited to 2047 characters. If not + specified, the existing value will not be changed. This parameter is not + allowed to be used at the same time as the parameter UseTcpDynamicPort. +* **`[Boolean]` SuppressRestart** _(Write)_: If set to $true then the any + attempt by the resource to restart the service is suppressed. The default + value is $false. +* **`[UInt16]` RestartTimeout** _(Write)_: Timeout value for restarting + the SQL Server services. The default value is 120 seconds. + +#### Read-Only Properties from Get-TargetResource + +* **`[Boolean]` IsActive** _(Read)_: Returns $true or $false whether the + IP address group is active. Not applicable for IP address group 'IPAll'. +* **`[String]` AddressFamily** _(Read)_: Returns the IP address's adress + family. Not applicable for IP address group 'IPAll'. +* **`[String]` TcpDynamicPort** _(Read)_: Returns the TCP/IP dynamic port. + Only applicable for the IP address group 'IPAll'. + +#### Examples + +* [Configure the IP address group IPAll with dynamic port](/source/Examples/Resources/SqlServerProtocolTcpIp/1-ConfigureIPAddressGroupIPAllWithDynamicPort.ps1) +* [Configure the IP address group IPAll with static port(s)](/source/Examples/Resources/SqlServerProtocolTcpIp/2-ConfigureIPAddressGroupIPAllWithStaticPort.ps1) +* [Configure the IP address group IP1 with IP address and static port(s)](/source/Examples/Resources/SqlServerProtocolTcpIp/3-ConfigureIPAddressGroupIP1.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+SqlServerProtocolTcpIp). + ### SqlServerReplication This resource manage SQL Replication distribution and publishing. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c99ab5005..21a802a87 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -38,7 +38,7 @@ stages: arguments: '-ResolveDependency -tasks pack' pwsh: true env: - ModuleVersion: $(gitVersion.InformationalVersion) + ModuleVersion: $(gitVersion.NuGetVersionV2) - task: PublishBuildArtifacts@1 displayName: 'Publish Build Artifact' inputs: @@ -164,6 +164,8 @@ stages: 'tests/Integration/DSC_SqlServerSecureConnection.Integration.Tests.ps1' 'tests/Integration/DSC_SqlScriptQuery.Integration.Tests.ps1' 'tests/Integration/DSC_SqlServerProtocol.Integration.Tests.ps1' + # Group 6 (tests makes changes that could make SQL Server to loose connectivity) + 'tests/Integration/DSC_SqlServerProtocolTcpIp.Integration.Tests.ps1' ) name: test displayName: 'Run Integration Test' @@ -228,6 +230,8 @@ stages: 'tests/Integration/DSC_SqlServerSecureConnection.Integration.Tests.ps1' 'tests/Integration/DSC_SqlScriptQuery.Integration.Tests.ps1' 'tests/Integration/DSC_SqlServerProtocol.Integration.Tests.ps1' + # Group 6 (tests makes changes that could make SQL Server to loose connectivity) + 'tests/Integration/DSC_SqlServerProtocolTcpIp.Integration.Tests.ps1' ) name: test displayName: 'Run Integration Test' diff --git a/source/DSCResources/DSC_SqlServerProtocol/DSC_SqlServerProtocol.psm1 b/source/DSCResources/DSC_SqlServerProtocol/DSC_SqlServerProtocol.psm1 index ea37aa954..e3282260c 100644 --- a/source/DSCResources/DSC_SqlServerProtocol/DSC_SqlServerProtocol.psm1 +++ b/source/DSCResources/DSC_SqlServerProtocol/DSC_SqlServerProtocol.psm1 @@ -660,116 +660,3 @@ function Compare-TargetResourceState return Compare-ResourcePropertyState @compareTargetResourceStateParameters } - -<# - .SYNOPSIS - Get static name properties of he specified protocol. - - .PARAMETER ProtocolName - Specifies the name of network protocol to return name properties for. - Possible values are 'TcpIp', 'NamedPipes', or 'ShareMemory'. - - .NOTES - The static values returned matches the values returned by the class - ServerProtocol. The property DisplayName could potentially be localized - while the property Name must be exactly like it is returned by the - class ServerProtocol, with the correct casing. - -#> -function Get-ProtocolNameProperties -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateSet('TcpIp', 'NamedPipes', 'SharedMemory')] - [System.String] - $ProtocolName - ) - - $protocolNameProperties = @{ } - - switch ($ProtocolName) - { - 'TcpIp' - { - $protocolNameProperties.DisplayName = 'TCP/IP' - $protocolNameProperties.Name = 'Tcp' - } - - 'NamedPipes' - { - $protocolNameProperties.DisplayName = 'Named Pipes' - $protocolNameProperties.Name = 'Np' - } - - 'SharedMemory' - { - $protocolNameProperties.DisplayName = 'Shared Memory' - $protocolNameProperties.Name = 'Sm' - } - } - - return $protocolNameProperties -} - -<# - .SYNOPSIS - Returns the ServerProtocol object for the specified SQL Server instance - and protocol name. - - .PARAMETER InstanceName - Specifies the name of the SQL Server instance to connect to. - - .PARAMETER ProtocolName - Specifies the name of network protocol to be configured. Possible values - are 'TcpIp', 'NamedPipes', or 'ShareMemory'. - - .PARAMETER ServerName - Specifies the host name of the SQL Server to connect to. - - .NOTES - The class Microsoft.SqlServer.Management.Smo.Wmi.ServerProtocol is - returned by this function. -#> -function Get-ServerProtocolObject -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $InstanceName, - - [Parameter(Mandatory = $true)] - [ValidateSet('TcpIp', 'NamedPipes', 'SharedMemory')] - [System.String] - $ProtocolName, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ServerName - ) - - $serverProtocolProperties = $null - - $newObjectParameters = @{ - TypeName = 'Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer' - ArgumentList = @($ServerName) - } - - $managedComputerObject = New-Object @newObjectParameters - - $serverInstance = $managedComputerObject.ServerInstances[$InstanceName] - - if ($serverInstance) - { - $protocolNameProperties = Get-ProtocolNameProperties -ProtocolName $ProtocolName - - $serverProtocolProperties = $serverInstance.ServerProtocols[$protocolNameProperties.Name] - } - - return $serverProtocolProperties -} diff --git a/source/DSCResources/DSC_SqlServerProtocol/README.md b/source/DSCResources/DSC_SqlServerProtocol/README.md index 357fd3729..e3aad2cb1 100644 --- a/source/DSCResources/DSC_SqlServerProtocol/README.md +++ b/source/DSCResources/DSC_SqlServerProtocol/README.md @@ -3,6 +3,12 @@ The `SqlServerProtocol` DSC resource manage the SQL Server protocols for a SQL Server instance. +For more information about protocol properties look at the following articles: + +* [TCP/IP Properties (Protocols Tab)](https://docs.microsoft.com/en-us/sql/tools/configuration-manager/tcp-ip-properties-protocols-tab). +* [Shared Memory Properties](https://docs.microsoft.com/en-us/sql/tools/configuration-manager/shared-memory-properties). +* [Named Pipes Properties](https://docs.microsoft.com/en-us/sql/tools/configuration-manager/named-pipes-properties). + ## Requirements * Target machine must be running Windows Server 2012 or later. diff --git a/source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.psm1 b/source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.psm1 new file mode 100644 index 000000000..532b183c2 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.psm1 @@ -0,0 +1,736 @@ +$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 TCP/IP protocol IP address + group for the specified SQL Server instance. + + .PARAMETER InstanceName + Specifies the name of the SQL Server instance to enable the protocol for. + + .PARAMETER IpAddressGroup + Specifies the name of the IP address group in the TCP/IP protocol, e.g. + 'IP1', 'IP2' etc., or 'IPAll'. + + .PARAMETER ServerName + Specifies the host name of the SQL Server to be configured. If the + SQL Server belongs to a cluster or availability group specify the host + name for the listener or cluster group. Default value is $env:COMPUTERNAME. + + .PARAMETER SuppressRestart + If set to $true then the any attempt by the resource to restart the service + is suppressed. The default value is $false. + + .PARAMETER RestartTimeout + Timeout value for restarting the SQL Server services. The default value + is 120 seconds. + + .NOTES + The parameters SuppressRestart and RestartTimeout are part of the function + Get-TargetResource to be able to return the value that the configuration + have set, or the default values if not. If they weren't passed to the + function Get-TargetResource we would have to always return $null which + would indicate that they are not set at all. + + Thought this function should throw an exception if the address group is + missing, but voted against it since run Test-DscConfiguration before + running a configuration (that would configure NICs) would then fail. + Instead choose to output a warning message indicating that the current + state cannot be evaluated. +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $IpAddressGroup, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter()] + [System.Boolean] + $SuppressRestart = $false, + + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 + ) + + $IpAddressGroup = Convert-IpAdressGroupCasing -IpAddressGroup $IpAddressGroup + + $returnValue = @{ + InstanceName = $InstanceName + IpAddressGroup = $IpAddressGroup + ServerName = $ServerName + SuppressRestart = $SuppressRestart + RestartTimeout = $RestartTimeout + Enabled = $false + IpAddress = $null + UseTcpDynamicPort = $false + TcpPort = $null + IsActive = $false + AddressFamily = $null + TcpDynamicPort = $null + } + + Write-Verbose -Message ( + $script:localizedData.GetCurrentState -f $IpAddressGroup, $InstanceName, $ServerName + ) + + Import-SQLPSModule + + <# + Must connect to the local machine name because $ServerName can point + to a cluster instance or availability group listener. + #> + $getServerProtocolObjectParameters = @{ + ServerName = $env:COMPUTERNAME + Instance = $InstanceName + ProtocolName = 'TcpIp' + } + + $serverProtocolProperties = Get-ServerProtocolObject @getServerProtocolObjectParameters + + if ($serverProtocolProperties) + { + if ($IpAddressGroup -in $serverProtocolProperties.IPAddresses.Name) + { + $ipAddressGroupObject = $serverProtocolProperties.IPAddresses[$IpAddressGroup] + + # Values for all IP adress groups. + $currentTcpPort = $ipAddressGroupObject.IPAddressProperties['TcpPort'].Value + $currentTcpDynamicPort = $ipAddressGroupObject.IPAddressProperties['TcpDynamicPorts'].Value + + # Get the current state of TcpDynamicPort. + if (-not ( + [System.String]::IsNullOrEmpty($currentTcpDynamicPort) ` + -or [System.String]::IsNullOrWhiteSpace($currentTcpDynamicPort) + ) + ) + { + $returnValue.UseTcpDynamicPort = $true + $returnValue.TcpDynamicPort = $currentTcpDynamicPort + } + + # Get the current state of TcpPort. + if (-not ( + [System.String]::IsNullOrEmpty($currentTcpPort) ` + -or [System.String]::IsNullOrWhiteSpace($currentTcpPort) + ) + ) + { + $returnValue.TcpPort = $currentTcpPort + } + + # Values for all individual IP adress groups. + switch ($IpAddressGroup) + { + 'IPAll' + { + <# + Left blank intentionally. There are no individual IP address + properties for the IP address group 'IPAll'. + #> + } + + Default + { + $returnValue.AddressFamily = $ipAddressGroupObject.IPAddress.AddressFamily + $returnValue.IpAddress = $ipAddressGroupObject.IPAddress.IPAddressToString + $returnValue.Enabled = $ipAddressGroupObject.IPAddressProperties['Enabled'].Value + $returnValue.IsActive = $ipAddressGroupObject.IPAddressProperties['Active'].Value + } + } + } + else + { + <# + The IP address groups are created automatically so if this happens + there is something wrong with the network interfaces on the node + that this resource can not solve. + #> + Write-Warning -Message ( + $script:localizedData.GetMissingIpAddressGroup -f $IpAddressGroup + ) + } + } + + return $returnValue +} + +<# + .SYNOPSIS + Sets the desired state of the SQL Server TCP/IP protocol IP address + group for the specified SQL Server instance. + + .PARAMETER InstanceName + Specifies the name of the SQL Server instance to enable the protocol for. + + .PARAMETER IpAddressGroup + Specifies the name of the IP address group in the TCP/IP protocol, e.g. + 'IP1', 'IP2' etc., or 'IPAll'. + + .PARAMETER ServerName + Specifies the host name of the SQL Server to be configured. If the SQL + Server belongs to a cluster or availability group specify the host name + for the listener or cluster group. Default value is `$env:COMPUTERNAME`. + + .PARAMETER Enabled + Specified if the IP address group should be enabled or disabled. Only used if + the IP address group is not set to 'IPAll'. If not specified, the existing + value will not be changed. + + .PARAMETER IpAddress + Specifies the IP address for the IP adress group. Only used if the IP address + group is not set to 'IPAll'. If not specified, the existing value will not be + changed. + + .PARAMETER UseTcpDynamicPort + Specifies whether the SQL Server instance should use a dynamic port. If + not specified the existing value will not be changed. This parameter is + not allowed to be used at the same time as the parameter TcpPort. + + .PARAMETER TcpPort + Specifies 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 as a string + value with the port numbers separated with a comma, e.g. '1433,1500,1501'. + This parameter is limited to 2047 characters. If not specified, the existing + value will not be changed.This parameter is not allowed to be used at the + same time as the parameter UseTcpDynamicPort. + + .PARAMETER SuppressRestart + If set to $true then the any attempt by the resource to restart the service + is suppressed. The default value is $false. + + .PARAMETER RestartTimeout + Timeout value for restarting the SQL Server services. The default value + is 120 seconds. +#> +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $IpAddressGroup, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter()] + [System.Boolean] + $Enabled, + + [Parameter()] + [System.String] + $IpAddress, + + [Parameter()] + [System.Boolean] + $UseTcpDynamicPort, + + [Parameter()] + [System.String] + $TcpPort, + + [Parameter()] + [System.Boolean] + $SuppressRestart = $false, + + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 + ) + + $IpAddressGroup = Convert-IpAdressGroupCasing -IpAddressGroup $IpAddressGroup + + <# + Compare the current state against the desired state. Calling this will + also import the necessary module to later call Get-ServerProtocolObject + which uses the SMO class ManagedComputer. + #> + $propertyState = Compare-TargetResourceState @PSBoundParameters + + # Get all properties that are not in desired state. + $propertiesNotInDesiredState = $propertyState.Where( { -not $_.InDesiredState }) + + if ($propertiesNotInDesiredState.Count -gt 0) + { + Write-Verbose -Message ( + $script:localizedData.SetDesiredState -f $IpAddressGroup, $InstanceName + ) + + <# + Must connect to the local machine name because $ServerName can point + to a cluster instance or availability group listener. + #> + $getServerProtocolObjectParameters = @{ + ServerName = $env:COMPUTERNAME + Instance = $InstanceName + ProtocolName = 'TcpIp' + } + + $serverProtocolProperties = Get-ServerProtocolObject @getServerProtocolObjectParameters + + if ($serverProtocolProperties) + { + if ($IpAddressGroup -in $serverProtocolProperties.IPAddresses.Name) + { + $ipAddressGroupObject = $serverProtocolProperties.IPAddresses[$IpAddressGroup] + + $isRestartNeeded = $false + + # Check if TcpPort property need updating. + if ($propertiesNotInDesiredState.Where( { $_.ParameterName -eq 'TcpPort' })) + { + $ipAddressGroupObject.IPAddressProperties['TcpPort'].Value = $TcpPort + + # Should be using TcpPort, make sure dynamic ports are disabled. + $ipAddressGroupObject.IPAddressProperties['TcpDynamicPorts'].Value = '' + + Write-Verbose -Message ( + $script:localizedData.TcpPortHasBeenSet -f $TcpPort, $IpAddressGroup + ) + + $isRestartNeeded = $true + } + + # Check if TcpDynamicPort property need updating. + if ($propertiesNotInDesiredState.Where( { $_.ParameterName -eq 'UseTcpDynamicPort' })) + { + <# + Enable TCP dynamic ports using a '0'. When the SQL Server + Database Engine is restarted it will get a dynamic port. + #> + $ipAddressGroupObject.IPAddressProperties['TcpDynamicPorts'].Value = '0' + + <# + Should be using dynamic TCP port, make sure static TCP port + are disabled. + #> + $ipAddressGroupObject.IPAddressProperties['TcpPort'].Value = '' + + Write-Verbose -Message ( + $script:localizedData.TcpDynamicPortHasBeenSet -f $IpAddressGroup + ) + + $isRestartNeeded = $true + } + + # Set individual protocol properties. + switch ($IpAddressGroup) + { + 'IPAll' + { + <# + Left blank intentionally. There are no individual protocol + properties for the IP address group IPAll. + #> + } + + Default + { + # Check if Enable property need updating. + if ($propertiesNotInDesiredState.Where( { $_.ParameterName -eq 'Enabled' })) + { + $ipAddressGroupObject.IPAddressProperties['Enabled'].Value = $Enabled + + if ($Enabled) + { + Write-Verbose -Message ( + $script:localizedData.GroupHasBeenEnabled -f $IpAddressGroup, $InstanceName + ) + } + else + { + Write-Verbose -Message ( + $script:localizedData.GroupHasBeenDisabled -f $IpAddressGroup, $InstanceName + ) + } + + $isRestartNeeded = $true + } + + # Check if Enabled property need updating. + if ($propertiesNotInDesiredState.Where( { $_.ParameterName -eq 'IpAddress' })) + { + # Casing of the property IpAddress is important! + $ipAddressGroupObject.IPAddressProperties['IpAddress'].Value = $IpAddress + + Write-Verbose -Message ( + $script:localizedData.IpAddressHasBeenSet -f $IpAddressGroup, $IpAddress + ) + + $isRestartNeeded = $true + } + } + } + + $serverProtocolProperties.Alter() + } + else + { + $errorMessage = $script:localizedData.SetMissingIpAddressGroup -f $IpAddressGroup + + New-ObjectNotFoundException -Message $errorMessage + } + } + else + { + $errorMessage = $script:localizedData.FailedToGetSqlServerProtocol + + New-InvalidOperationException -Message $errorMessage + } + + if (-not $SuppressRestart -and $isRestartNeeded) + { + $restartSqlServiceParameters = @{ + ServerName = $ServerName + InstanceName = $InstanceName + Timeout = $RestartTimeout + OwnerNode = $env:COMPUTERNAME + } + + Restart-SqlService @restartSqlServiceParameters + } + elseif ($isRestartNeeded) + { + Write-Warning -Message $script:localizedData.RestartSuppressed + } + } + else + { + Write-Verbose -Message ( + $script:localizedData.GroupIsInDesiredState -f $IpAddressGroup, $InstanceName + ) + } +} + +<# + .SYNOPSIS + Determines the desired state of the SQL Server TCP/IP protocol IP address + group for the specified SQL Server instance. + + .PARAMETER InstanceName + Specifies the name of the SQL Server instance to enable the protocol for. + + .PARAMETER IpAddressGroup + Specifies the name of the IP address group in the TCP/IP protocol, e.g. + 'IP1', 'IP2' etc., or 'IPAll'. + + .PARAMETER ServerName + Specifies the host name of the SQL Server to be configured. If the + SQL Server belongs to a cluster or availability group specify the host + name for the listener or cluster group. Default value is $env:COMPUTERNAME. + + .PARAMETER Enabled + Specified if the IP address group should be enabled or disabled. Only used if + the IP address group is not set to 'IPAll'. If not specified, the existing + value will not be changed. + + .PARAMETER IpAddress + Specifies the IP address for the IP adress group. Only used if the IP address + group is not set to 'IPAll'. If not specified, the existing value will not be + changed. + + .PARAMETER UseTcpDynamicPort + Specifies whether the SQL Server instance should use a dynamic port. If + not specified the existing value will not be changed. This parameter is + not allowed to be used at the same time as the parameter TcpPort. + + .PARAMETER TcpPort + Specifies 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 as a string + value with the port numbers separated with a comma, e.g. '1433,1500,1501'. + This parameter is limited to 2047 characters. If not specified, the existing + value will not be changed.This parameter is not allowed to be used at the + same time as the parameter UseTcpDynamicPort. + + .PARAMETER SuppressRestart + If set to $true then the any attempt by the resource to restart the service + is suppressed. The default value is $false. + + .PARAMETER RestartTimeout + Timeout value for restarting the SQL Server services. The default value + is 120 seconds. +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $IpAddressGroup, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter()] + [System.Boolean] + $Enabled, + + [Parameter()] + [System.String] + $IpAddress, + + [Parameter()] + [System.Boolean] + $UseTcpDynamicPort, + + [Parameter()] + [System.String] + $TcpPort, + + [Parameter()] + [System.Boolean] + $SuppressRestart = $false, + + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 + ) + + Write-Verbose -Message ( + $script:localizedData.TestDesiredState -f $IpAddressGroup, $InstanceName, $ServerName + ) + + $propertyState = Compare-TargetResourceState @PSBoundParameters + + + if ($false -in $propertyState.InDesiredState) + { + $testTargetResourceReturnValue = $false + + Write-Verbose -Message ( + $script:localizedData.NotInDesiredState -f $IpAddressGroup, $InstanceName + ) + } + else + { + $testTargetResourceReturnValue = $true + + Write-Verbose -Message ( + $script:localizedData.InDesiredState -f $IpAddressGroup, $InstanceName + ) + } + + return $testTargetResourceReturnValue +} + +<# + .SYNOPSIS + Compares the properties in the current state with the properties of the + desired state and returns a hashtable with the comparison result. + + .PARAMETER InstanceName + Specifies the name of the SQL Server instance to enable the protocol for. + + .PARAMETER IpAddressGroup + Specifies the name of the IP address group in the TCP/IP protocol, e.g. + 'IP1', 'IP2' etc., or 'IPAll'. + + .PARAMETER ServerName + Specifies the host name of the SQL Server to be configured. If the + SQL Server belongs to a cluster or availability group specify the host + name for the listener or cluster group. Default value is $env:COMPUTERNAME. + + .PARAMETER Enabled + Specified if the IP address group should be enabled or disabled. Only used if + the IP address group is not set to 'IPAll'. If not specified, the existing + value will not be changed. + + .PARAMETER IpAddress + Specifies the IP address for the IP adress group. Only used if the IP address + group is not set to 'IPAll'. If not specified, the existing value will not be + changed. + + .PARAMETER UseTcpDynamicPort + Specifies whether the SQL Server instance should use a dynamic port. If + not specified the existing value will not be changed. This parameter is + not allowed to be used at the same time as the parameter TcpPort. + + .PARAMETER TcpPort + Specifies 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 as a string + value with the port numbers separated with a comma, e.g. '1433,1500,1501'. + This parameter is limited to 2047 characters. If not specified, the existing + value will not be changed.This parameter is not allowed to be used at the + same time as the parameter UseTcpDynamicPort. + + .PARAMETER SuppressRestart + If set to $true then the any attempt by the resource to restart the service + is suppressed. The default value is $false. + + .PARAMETER RestartTimeout + Timeout value for restarting the SQL Server services. The default value + is 120 seconds. +#> +function Compare-TargetResourceState +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [System.String] + $IpAddressGroup, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName = $env:COMPUTERNAME, + + [Parameter()] + [System.Boolean] + $Enabled, + + [Parameter()] + [System.String] + $IpAddress, + + [Parameter()] + [System.Boolean] + $UseTcpDynamicPort, + + [Parameter()] + [System.String] + $TcpPort, + + [Parameter()] + [System.Boolean] + $SuppressRestart = $false, + + [Parameter()] + [System.UInt16] + $RestartTimeout = 120 + ) + + $assertBoundParameterParameters = @{ + BoundParameterList = $PSBoundParameters + MutuallyExclusiveList1 = @( + 'UseTcpDynamicPort' + ) + MutuallyExclusiveList2 = @( + 'TcpPort' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + if ($PSBoundParameters.ContainsKey('IpAddress')) + { + Assert-IpAddress -Address $IpAddress + } + + $getTargetResourceParameters = @{ + InstanceName = $InstanceName + IpAddressGroup = $IpAddressGroup + ServerName = $ServerName + SuppressRestart = $SuppressRestart + RestartTimeout = $RestartTimeout + } + + <# + We remove any parameters not passed by $PSBoundParameters so that + Get-TargetResource can also evaluate $PSBoundParameters correctly. + + Need the @() around the Keys property to get a new array to enumerate. + #> + @($getTargetResourceParameters.Keys) | ForEach-Object { + if (-not $PSBoundParameters.ContainsKey($_)) + { + $getTargetResourceParameters.Remove($_) + } + } + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + # Get individual IP address group properties to evaluate. + switch ($IpAddressGroup) + { + 'IPAll' + { + $propertiesToEvaluate = @( + 'UseTcpDynamicPort' + 'TcpPort' + ) + } + + Default + { + $propertiesToEvaluate = @( + 'Enabled' + 'IpAddress' + 'UseTcpDynamicPort' + 'TcpPort' + ) + } + } + + $compareTargetResourceStateParameters = @{ + CurrentValues = $getTargetResourceResult + DesiredValues = $PSBoundParameters + Properties = $propertiesToEvaluate + } + + return Compare-ResourcePropertyState @compareTargetResourceStateParameters +} + +<# + .SYNOPSIS + Converts a IP address group name to the correct casing. + + .PARAMETER IpAddressGroup + Specifies the name of the IP address group in the TCP/IP protocol, e.g. + 'IP1', 'IP2' etc., or 'IPAll'. + + .NOTES + SMO is case-sensitive with the address group names. +#> +function Convert-IpAdressGroupCasing +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $IpAddressGroup + ) + + return ($IpAddressGroup.ToUpper() -replace 'IPALL', 'IPAll') +} diff --git a/source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.schema.mof b/source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.schema.mof new file mode 100644 index 000000000..7dcdf17bb --- /dev/null +++ b/source/DSCResources/DSC_SqlServerProtocolTcpIp/DSC_SqlServerProtocolTcpIp.schema.mof @@ -0,0 +1,16 @@ +[ClassVersion("1.0.0.0"), FriendlyName("SqlServerProtocolTcpIp")] +class DSC_SqlServerProtocolTcpIp : OMI_BaseResource +{ + [Key, Description("Specifies the name of the SQL Server instance to manage the IP address group for.")] String InstanceName; + [Key, Description("Specifies the name of the IP address group in the TCP/IP protocol, e.g. 'IP1', 'IP2' etc., or 'IPAll'.")] String IpAddressGroup; + [Write, Description("Specifies the host name of the SQL Server to be configured. If the SQL Server belongs to a cluster or availability group specify the host name for the listener or cluster group. Default value is $env:COMPUTERNAME.")] String ServerName; + [Write, Description("Specified if the IP address group should be enabled or disabled. Only used if the IP address group is not set to 'IPAll'. If not specified, the existing value will not be changed.")] Boolean Enabled; + [Write, Description("Specifies the IP address for the IP adress group. Only used if the IP address group is not set to 'IPAll'. If not specified, the existing value will not be changed.")] String IpAddress; + [Write, Description("Specifies whether the SQL Server instance should use a dynamic port. If not specified, the existing value will not be changed. This parameter is not allowed to be used at the same time as the parameter TcpPort.")] Boolean UseTcpDynamicPort; + [Write, Description("Specifies 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 as a string value with the port numbers separated with a comma, e.g. '1433,1500,1501'. This parameter is limited to 2047 characters. If not specified, the existing value will not be changed. This parameter is not allowed to be used at the same time as the parameter UseTcpDynamicPort.")] String TcpPort; + [Write, Description("If set to $true then the any attempt by the resource to restart the service is suppressed. The default value is $false.")] Boolean SuppressRestart; + [Write, Description("Timeout value for restarting the SQL Server services. The default value is 120 seconds.")] UInt16 RestartTimeout; + [Read, Description("Returns $true or $false whether the IP address group is active. Not applicable for IP address group 'IPAll'.")] Boolean IsActive; + [Read, Description("Returns the IP address's adress family. Not applicable for IP address group 'IPAll'.")] String AddressFamily; + [Read, Description("Returns the TCP/IP dynamic port. Only applicable for the IP address group 'IPAll'.")] String TcpDynamicPort; +}; diff --git a/source/DSCResources/DSC_SqlServerProtocolTcpIp/README.md b/source/DSCResources/DSC_SqlServerProtocolTcpIp/README.md new file mode 100644 index 000000000..46139eed4 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerProtocolTcpIp/README.md @@ -0,0 +1,22 @@ +# Description + +The `SqlServerProtocolTcpIp` DSC resource manage the TCP/IP +protocol IP address groups for a SQL Server instance. + +IP Address groups are added depending on available network cards, see +[Adding or Removing IP Addresses](https://docs.microsoft.com/en-us/sql/tools/configuration-manager/tcp-ip-properties-ip-addresses-tab#adding-or-removing-ip-addresses). +Because of that it is not supported to add or remove IP address groups. + +For more information about static and dynamic ports read the article +[TCP/IP Properties (IP Addresses Tab)](https://docs.microsoft.com/en-us/sql/tools/configuration-manager/tcp-ip-properties-ip-addresses-tab). + +## Requirements + +* Target machine must be running Windows Server 2012 or later. +* Target machine must be running SQL Server 2012 or later. +* Target machine must have access to the SQLPS PowerShell module or the SqlServer + PowerShell module. +* To configure a single IP address to listen on multiple ports, the + TcpIp protocol must also set the **Listen All** property to **No**. + This can be done with the resource `SqlServerProtocol` using the + parameter `ListenOnAllIpAddresses`. diff --git a/source/DSCResources/DSC_SqlServerProtocolTcpIp/en-US/DSC_SqlServerProtocolTcpIp.strings.psd1 b/source/DSCResources/DSC_SqlServerProtocolTcpIp/en-US/DSC_SqlServerProtocolTcpIp.strings.psd1 new file mode 100644 index 000000000..8ad32b265 --- /dev/null +++ b/source/DSCResources/DSC_SqlServerProtocolTcpIp/en-US/DSC_SqlServerProtocolTcpIp.strings.psd1 @@ -0,0 +1,17 @@ +ConvertFrom-StringData @' + GetCurrentState = Getting the current state of the TCP/IP address group '{0}' for the instance '{1}' on the server '{2}'. (SSPTI0001) + GetMissingIpAddressGroup = The specified IP address group '{0}' does not not exist, cannot determine current state. (SSPTI0002) + TestDesiredState = Determining the current state of the TCP/IP address group '{0}' for the instance '{1}' on the server '{2}'. (SSPTI0003) + NotInDesiredState = The TCP/IP address group '{0}' for the instance '{1}' is not in desired state. (SSPTI0004) + InDesiredState = The TCP/IP address group '{0}' for the instance '{1}' is in desired state. (SSPTI0005) + SetDesiredState = Setting the desired state for the TCP/IP address group '{0}' for the instance '{1}'. (SSPTI0006) + SetMissingIpAddressGroup = The specified IP address group '{0}' does not not exist. (SSPTI0007) + TcpPortHasBeenSet = The TCP port(s) '{0}' has been set on the TCP/IP address group '{1}'. (SSPTI0008) + TcpDynamicPortHasBeenSet = Dynamic TCP port has been enabled on the TCP/IP address group '{0}'. (SSPTI0009) + GroupHasBeenEnabled = The TCP/IP address group '{0}' has been enabled on the SQL Server instance '{1}'. (SSPTI0010) + GroupHasBeenDisabled = The TCP/IP address group '{0}' has been disabled on the SQL Server instance '{1}'. (SSPTI0011) + IpAddressHasBeenSet = The TCP/IP address for the TCP/IP address group '{0}' has been set to '{1}'. (SSPTI0012) + GroupIsInDesiredState = The TCP/IP address group '{0}' on the instance '{1}' is already in desired state. (SSPTI0013) + RestartSuppressed = The restart was suppressed. The configuration will not be active until the node is manually restart. (SSPTI0014) + FailedToGetSqlServerProtocol = Failed to get the settings for the SQL Server Database Engine server protocol TCP/IP. (SSPTI0015) +'@ diff --git a/source/Examples/Resources/SqlServerProtocolTcpIp/1-ConfigureIPAddressGroupIPAllWithDynamicPort.ps1 b/source/Examples/Resources/SqlServerProtocolTcpIp/1-ConfigureIPAddressGroupIPAllWithDynamicPort.ps1 new file mode 100644 index 000000000..a654cc7d8 --- /dev/null +++ b/source/Examples/Resources/SqlServerProtocolTcpIp/1-ConfigureIPAddressGroupIPAllWithDynamicPort.ps1 @@ -0,0 +1,29 @@ +<# + .DESCRIPTION + This example will set the TCP/IP address group IPAll to use dynamic port. + + 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 + { + SqlServerProtocolTcpIP 'ChangeIPAll' + { + InstanceName = 'MSSQLSERVER' + IpAddressGroup = 'IPAll' + UseTcpDynamicPort = $true + + PsDscRunAsCredential = $SystemAdministratorAccount + } + } +} diff --git a/source/Examples/Resources/SqlServerProtocolTcpIp/2-ConfigureIPAddressGroupIPAllWithStaticPort.ps1 b/source/Examples/Resources/SqlServerProtocolTcpIp/2-ConfigureIPAddressGroupIPAllWithStaticPort.ps1 new file mode 100644 index 000000000..adc571252 --- /dev/null +++ b/source/Examples/Resources/SqlServerProtocolTcpIp/2-ConfigureIPAddressGroupIPAllWithStaticPort.ps1 @@ -0,0 +1,29 @@ +<# + .DESCRIPTION + This example will set the TCP/IP address group IPAll to use static ports. + + 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 + { + SqlServerProtocolTcpIP 'ChangeIPAll' + { + InstanceName = 'MSSQLSERVER' + IpAddressGroup = 'IPAll' + TcpPort = '1433,1500,1501' + + PsDscRunAsCredential = $SystemAdministratorAccount + } + } +} diff --git a/source/Examples/Resources/SqlServerProtocolTcpIp/3-ConfigureIPAddressGroupIP1.ps1 b/source/Examples/Resources/SqlServerProtocolTcpIp/3-ConfigureIPAddressGroupIP1.ps1 new file mode 100644 index 000000000..4c3ec165e --- /dev/null +++ b/source/Examples/Resources/SqlServerProtocolTcpIp/3-ConfigureIPAddressGroupIP1.ps1 @@ -0,0 +1,41 @@ +<# + .DESCRIPTION + This example will set the TCP/IP address group IPAll to use static ports. + + 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 + { + SqlServerProtocol 'DisableListenAllIPAddresses' + { + InstanceName = 'MSSQLSERVER' + ProtocolName = 'TcpIp' + Enabled = $true + ListenOnAllIpAddresses = $false + + PsDscRunAsCredential = $SystemAdministratorAccount + } + + SqlServerProtocolTcpIP 'ChangeIP1' + { + InstanceName = 'MSSQLSERVER' + IpAddressGroup = 'IP1' + Enabled = $true + IpAddress = 'fe80::7894:a6b6:59dd:c8fe%9' + TcpPort = '1433,1500,1501' + + PsDscRunAsCredential = $SystemAdministratorAccount + } + } +} diff --git a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 index 32710fab6..6bb31aa60 100644 --- a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 +++ b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 @@ -49,6 +49,8 @@ 'Find-ExceptionByNumber' 'Compare-ResourcePropertyState' 'Test-DscPropertyState' + 'Get-ProtocolNameProperties' + 'Get-ServerProtocolObject' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 index b1475d360..be92bd53f 100644 --- a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 +++ b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 @@ -2345,3 +2345,115 @@ function Test-DscPropertyState return $returnValue } + +<# + .SYNOPSIS + Get static name properties of he specified protocol. + + .PARAMETER ProtocolName + Specifies the name of network protocol to return name properties for. + Possible values are 'TcpIp', 'NamedPipes', or 'ShareMemory'. + + .NOTES + The static values returned matches the values returned by the class + ServerProtocol. The property DisplayName could potentially be localized + while the property Name must be exactly like it is returned by the + class ServerProtocol, with the correct casing. +#> +function Get-ProtocolNameProperties +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('TcpIp', 'NamedPipes', 'SharedMemory')] + [System.String] + $ProtocolName + ) + + $protocolNameProperties = @{ } + + switch ($ProtocolName) + { + 'TcpIp' + { + $protocolNameProperties.DisplayName = 'TCP/IP' + $protocolNameProperties.Name = 'Tcp' + } + + 'NamedPipes' + { + $protocolNameProperties.DisplayName = 'Named Pipes' + $protocolNameProperties.Name = 'Np' + } + + 'SharedMemory' + { + $protocolNameProperties.DisplayName = 'Shared Memory' + $protocolNameProperties.Name = 'Sm' + } + } + + return $protocolNameProperties +} + +<# + .SYNOPSIS + Returns the ServerProtocol object for the specified SQL Server instance + and protocol name. + + .PARAMETER InstanceName + Specifies the name of the SQL Server instance to connect to. + + .PARAMETER ProtocolName + Specifies the name of network protocol to be configured. Possible values + are 'TcpIp', 'NamedPipes', or 'ShareMemory'. + + .PARAMETER ServerName + Specifies the host name of the SQL Server to connect to. + + .NOTES + The class Microsoft.SqlServer.Management.Smo.Wmi.ServerProtocol is + returned by this function. +#> +function Get-ServerProtocolObject +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $InstanceName, + + [Parameter(Mandatory = $true)] + [ValidateSet('TcpIp', 'NamedPipes', 'SharedMemory')] + [System.String] + $ProtocolName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ServerName + ) + + $serverProtocolProperties = $null + + $newObjectParameters = @{ + TypeName = 'Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer' + ArgumentList = @($ServerName) + } + + $managedComputerObject = New-Object @newObjectParameters + + $serverInstance = $managedComputerObject.ServerInstances[$InstanceName] + + if ($serverInstance) + { + $protocolNameProperties = Get-ProtocolNameProperties -ProtocolName $ProtocolName + + $serverProtocolProperties = $serverInstance.ServerProtocols[$protocolNameProperties.Name] + } + + return $serverProtocolProperties +} diff --git a/source/SqlServerDsc.psd1 b/source/SqlServerDsc.psd1 index 549dc1066..91cf6e530 100644 --- a/source/SqlServerDsc.psd1 +++ b/source/SqlServerDsc.psd1 @@ -67,6 +67,7 @@ 'SqlServerNetwork' 'SqlServerPermission' 'SqlServerProtocol' + 'SqlServerProtocolTcpIp' 'SqlServerReplication' 'SqlServerRole' 'SqlServerSecureConnection' diff --git a/tests/Integration/DSC_SqlServerProtocolTcpIp.Integration.Tests.ps1 b/tests/Integration/DSC_SqlServerProtocolTcpIp.Integration.Tests.ps1 new file mode 100644 index 000000000..215a2256d --- /dev/null +++ b/tests/Integration/DSC_SqlServerProtocolTcpIp.Integration.Tests.ps1 @@ -0,0 +1,89 @@ +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 = 'SqlServerProtocolTcpIp' +$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' + +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)_ListenOnSpecificIpAddress_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.IpAddressGroup | Should -Be 'IP1' + $resourceCurrentState.Enabled | Should -BeTrue + $resourceCurrentState.IpAddress | Should -Be $ConfigurationData.AllNodes.IpAddress + } + + 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_SqlServerProtocolTcpIp.config.ps1 b/tests/Integration/DSC_SqlServerProtocolTcpIp.config.ps1 new file mode 100644 index 000000000..59542d0ec --- /dev/null +++ b/tests/Integration/DSC_SqlServerProtocolTcpIp.config.ps1 @@ -0,0 +1,76 @@ +#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 +{ + $currentIp4Address = Get-NetIPAddress -AddressFamily 'IPv4' | + Where-Object -Property 'PrefixOrigin' -EQ 'Dhcp' | + Select-Object -FIrst 1 -ExpandProperty 'IPAddress' + + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + + UserName = "$env:COMPUTERNAME\SqlInstall" + Password = 'P@ssw0rd1' + + IpAddress = $currentIp4Address + + InstanceName = 'DSCSQLTEST' + + CertificateFile = $env:DscPublicCertificatePath + } + ) + } +} + +<# + .SYNOPSIS + Disables listen on all IP addresses and then configures IP address group + 'IP1' with the first IP address assigned to the node (first IP address + which has DHCP as the prefix origin). +#> +Configuration DSC_SqlServerProtocolTcpIp_ListenOnSpecificIpAddress_Config +{ + Import-DscResource -ModuleName 'SqlServerDsc' + + node $AllNodes.NodeName + { + SqlServerProtocol 'DisableListenAllIPAddresses' + { + InstanceName = $Node.InstanceName + ProtocolName = 'TcpIp' + Enabled = $true + ListenOnAllIpAddresses = $false + SuppressRestart = $true + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + + SqlServerProtocolTcpIP 'Integration_Test' + { + InstanceName = $Node.InstanceName + IpAddressGroup = 'IP1' + Enabled = $true + IpAddress = $Node.IpAddress + SuppressRestart = $true + + PsDscRunAsCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.Username, (ConvertTo-SecureString -String $Node.Password -AsPlainText -Force)) + } + } +} diff --git a/tests/Unit/DSC_SqlServerProtocol.Tests.ps1 b/tests/Unit/DSC_SqlServerProtocol.Tests.ps1 index 2f6c6443f..2ef6f2dca 100644 --- a/tests/Unit/DSC_SqlServerProtocol.Tests.ps1 +++ b/tests/Unit/DSC_SqlServerProtocol.Tests.ps1 @@ -1064,88 +1064,6 @@ try } } } - - Describe 'SqlServerProtocol\Get-ProtocolNameProperties' -Tag 'Helper' { - $testCases = @( - @{ - ParameterValue = 'TcpIp' - DisplayName = 'TCP/IP' - Name = 'Tcp' - } - @{ - ParameterValue = 'NamedPipes' - DisplayName = 'Named Pipes' - Name = 'Np' - } - @{ - ParameterValue = 'SharedMemory' - DisplayName = 'Shared Memory' - Name = 'Sm' - } - ) - - It 'Should return the correct values when the protocol is ''''' -TestCases $testCases { - param - ( - [Parameter()] - [System.String] - $ParameterValue, - - [Parameter()] - [System.String] - $DisplayName, - - [Parameter()] - [System.String] - $Name - ) - - $result = Get-ProtocolNameProperties -ProtocolName $ParameterValue - - $result.DisplayName = $DisplayName - $result.Name = $Name - } - } - - Describe 'SqlServerProtocol\Get-ServerProtocolObject' -Tag 'Helper' { - BeforeAll { - $mockInstanceName = 'TestInstance' - - Mock -CommandName New-Object -MockWith { - return @{ - ServerInstances = @{ - $mockInstanceName = @{ - ServerProtocols = @{ - Tcp = @{ - IsEnabled = $true - HasMultiIPAddresses = $true - ProtocolProperties = @{ - ListenOnAllIPs = $true - KeepAlive = 30000 - } - } - } - } - } - } - } - } - - It 'Should return a ManagedComputer object with the correct values' { - $mockGetServerProtocolObjectParameters = @{ - ServerName = 'AnyServer' - Instance = $mockInstanceName - ProtocolName = 'TcpIp' - } - - $result = Get-ServerProtocolObject @mockGetServerProtocolObjectParameters - - $result.IsEnabled | Should -BeTrue - $result.HasMultiIPAddresses | Should -BeTrue - $result.ProtocolProperties.ListenOnAllIPs | Should -BeTrue - $result.ProtocolProperties.KeepAlive | Should -Be 30000 - } - } } } finally diff --git a/tests/Unit/DSC_SqlServerProtocolTcpIp.Tests.ps1 b/tests/Unit/DSC_SqlServerProtocolTcpIp.Tests.ps1 new file mode 100644 index 000000000..1a637a27a --- /dev/null +++ b/tests/Unit/DSC_SqlServerProtocolTcpIp.Tests.ps1 @@ -0,0 +1,1273 @@ +$script:dscModuleName = 'SqlServerDsc' +$script:dscResourceName = 'DSC_SqlServerProtocolTcpIp' + +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 +} + +# Begin Testing + +Invoke-TestSetup + +try +{ + InModuleScope $script:dscResourceName { + Set-StrictMode -Version 1.0 + + Describe 'SqlServerProtocolTcpIp\Get-TargetResource' -Tag 'Get' { + BeforeAll { + $mockInstanceName = 'DSCTEST' + + Mock -CommandName Import-SQLPSModule + } + + Context 'When the system is not in the desired state' { + Context 'When the SQL Server instance does not exist' { + BeforeAll { + Mock -CommandName Get-ServerProtocolObject -MockWith { + return $null + } + + $getTargetResourceParameters = @{ + InstanceName = $mockInstanceName + <# + Intentionally using lower-case to test so that + the correct casing is returned. + #> + IpAddressGroup = 'ipall' + } + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.InstanceName | Should -Be $mockInstanceName + # IP address group should always be returned with the correct casing. + $getTargetResourceResult.IpAddressGroup | Should -BeExactly 'IPAll' + $getTargetResourceResult.ServerName | Should -Be $env:COMPUTERNAME + $getTargetResourceResult.SuppressRestart | Should -BeFalse + $getTargetResourceResult.RestartTimeout | Should -Be 120 + $getTargetResourceResult.Enabled | Should -BeFalse + $getTargetResourceResult.IPAddress | Should -BeNullOrEmpty + $getTargetResourceResult.UseTcpDynamicPort | Should -BeFalse + $getTargetResourceResult.TcpPort | Should -BeNullOrEmpty + $getTargetResourceResult.IsActive | Should -BeFalse + $getTargetResourceResult.AddressFamily | Should -BeNullOrEmpty + $getTargetResourceResult.TcpDynamicPort | Should -BeNullOrEmpty + } + } + + Context 'When the IP address group is missing' { + BeforeAll { + Mock -CommandName Write-Warning + Mock -CommandName Get-ServerProtocolObject -MockWith { + return @{ + IPAddresses = @( + [PSCustomObject] @{ + Name = 'IPAll' + } + [PSCustomObject] @{ + Name = 'IP1' + } + ) + } + } + + $getTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP2' + } + } + + It 'Should return the correct values' { + { Get-TargetResource @getTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Write-Warning + } + } + } + + Context 'When the system is in the desired state' { + Context 'When the IP address group is IPAll' { + Context 'When the IP address group is using dynamic port' { + BeforeAll { + Mock -CommandName Get-ServerProtocolObject -MockWith { + return @{ + IPAddresses = @{ + Name = 'IPAll' + IPAll = @{ + IPAddressProperties = @{ + TcpPort = @{ + Value = '' + } + TcpDynamicPorts = @{ + Value = '0' + } + } + } + } + } + } + + $getTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + } + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.InstanceName | Should -Be $mockInstanceName + $getTargetResourceResult.IpAddressGroup | Should -BeExactly 'IPAll' + $getTargetResourceResult.ServerName | Should -Be $env:COMPUTERNAME + $getTargetResourceResult.SuppressRestart | Should -BeFalse + $getTargetResourceResult.RestartTimeout | Should -Be 120 + $getTargetResourceResult.Enabled | Should -BeFalse + $getTargetResourceResult.IPAddress | Should -BeNullOrEmpty + $getTargetResourceResult.UseTcpDynamicPort | Should -BeTrue + $getTargetResourceResult.TcpPort | Should -BeNullOrEmpty + $getTargetResourceResult.IsActive | Should -BeFalse + $getTargetResourceResult.AddressFamily | Should -BeNullOrEmpty + $getTargetResourceResult.TcpDynamicPort | Should -BeExactly '0' + } + } + + Context 'When the IP address group is using static TCP ports' { + BeforeAll { + Mock -CommandName Get-ServerProtocolObject -MockWith { + return @{ + IPAddresses = @{ + Name = 'IPAll' + IPAll = @{ + IPAddressProperties = @{ + TcpPort = @{ + Value = '1433,1500,1501' + } + TcpDynamicPorts = @{ + Value = '' + } + } + } + } + } + } + + $getTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + } + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.InstanceName | Should -Be $mockInstanceName + $getTargetResourceResult.IpAddressGroup | Should -BeExactly 'IPAll' + $getTargetResourceResult.ServerName | Should -Be $env:COMPUTERNAME + $getTargetResourceResult.SuppressRestart | Should -BeFalse + $getTargetResourceResult.RestartTimeout | Should -Be 120 + $getTargetResourceResult.Enabled | Should -BeFalse + $getTargetResourceResult.IPAddress | Should -BeNullOrEmpty + $getTargetResourceResult.UseTcpDynamicPort | Should -BeFalse + $getTargetResourceResult.TcpPort | Should -BeExactly '1433,1500,1501' + $getTargetResourceResult.IsActive | Should -BeFalse + $getTargetResourceResult.AddressFamily | Should -BeNullOrEmpty + $getTargetResourceResult.TcpDynamicPort | Should -BeNullOrEmpty + } + } + } + + Context 'When the IP address group is IPx (where x is an available group number)' { + Context 'When the IP address group is using dynamic port' { + BeforeAll { + Mock -CommandName Get-ServerProtocolObject -MockWith { + return @{ + IPAddresses = @{ + Name = 'IP1' + IP1 = @{ + IPAddress = @{ + AddressFamily = 'InterNetworkV6' + IPAddressToString = 'fe80::7894:a6b6:59dd:c8ff%9' + } + IPAddressProperties = @{ + TcpPort = @{ + Value = '' + } + TcpDynamicPorts = @{ + Value = '0' + } + Enabled = @{ + Value = $true + } + Active = @{ + Value = $true + } + } + } + } + } + } + + $getTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + } + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.InstanceName | Should -Be $mockInstanceName + $getTargetResourceResult.IpAddressGroup | Should -BeExactly 'IP1' + $getTargetResourceResult.ServerName | Should -Be $env:COMPUTERNAME + $getTargetResourceResult.SuppressRestart | Should -BeFalse + $getTargetResourceResult.RestartTimeout | Should -Be 120 + $getTargetResourceResult.Enabled | Should -BeTrue + $getTargetResourceResult.IPAddress | Should -Be 'fe80::7894:a6b6:59dd:c8ff%9' + $getTargetResourceResult.UseTcpDynamicPort | Should -BeTrue + $getTargetResourceResult.TcpPort | Should -BeNullOrEmpty + $getTargetResourceResult.IsActive | Should -BeTrue + $getTargetResourceResult.AddressFamily | Should -Be 'InterNetworkV6' + $getTargetResourceResult.TcpDynamicPort | Should -BeExactly '0' + } + } + + Context 'When the IP address group is using static TCP ports' { + BeforeAll { + Mock -CommandName Get-ServerProtocolObject -MockWith { + return @{ + IPAddresses = @{ + Name = 'IP1' + IP1 = @{ + IPAddress = @{ + AddressFamily = 'InterNetworkV6' + IPAddressToString = 'fe80::7894:a6b6:59dd:c8ff%9' + } + IPAddressProperties = @{ + TcpPort = @{ + Value = '1433,1500,1501' + } + TcpDynamicPorts = @{ + Value = '' + } + Enabled = @{ + Value = $true + } + Active = @{ + Value = $true + } + } + } + } + } + } + + $getTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + } + } + + It 'Should return the correct values' { + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + $getTargetResourceResult.InstanceName | Should -Be $mockInstanceName + $getTargetResourceResult.IpAddressGroup | Should -BeExactly 'IP1' + $getTargetResourceResult.ServerName | Should -Be $env:COMPUTERNAME + $getTargetResourceResult.SuppressRestart | Should -BeFalse + $getTargetResourceResult.RestartTimeout | Should -Be 120 + $getTargetResourceResult.Enabled | Should -BeTrue + $getTargetResourceResult.IPAddress | Should -Be 'fe80::7894:a6b6:59dd:c8ff%9' + $getTargetResourceResult.UseTcpDynamicPort | Should -BeFalse + $getTargetResourceResult.TcpPort | Should -BeExactly '1433,1500,1501' + $getTargetResourceResult.IsActive | Should -BeTrue + $getTargetResourceResult.AddressFamily | Should -Be 'InterNetworkV6' + $getTargetResourceResult.TcpDynamicPort | Should -BeNullOrEmpty + } + } + } + } + } + + Describe 'SqlServerProtocolTcpIp\Test-TargetResource' -Tag 'Test' { + BeforeAll { + $testTargetResourceParameters = @{ + InstanceName = 'DSCTEST' + IpAddressGroup = 'IPAll' + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Enabled' + InDesiredState = $true + } + ) + } + } + + It 'Should return $true' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeTrue + + Assert-MockCalled -CommandName Compare-TargetResourceState -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Enabled' + InDesiredState = $false + } + ) + } + } + + It 'Should return $false' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeFalse + + Assert-MockCalled -CommandName Compare-TargetResourceState -Exactly -Times 1 -Scope It + } + } + } + + Describe 'SqlServerProtocolTcpIp\Compare-TargetResourceState' -Tag 'Compare' { + BeforeAll { + $mockInstanceName = 'DSCTEST' + } + + Context 'When passing wrong set of parameters' { + It 'Should throw the an exception when passing both UseTcpDynamicPort and TcpPort' { + $testTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + UseTcpDynamicPort = $true + TcpPort = '1433' + } + + { Compare-ResourcePropertyState @testTargetResourceParameters } | Should -Throw + } + } + + Context 'When the system is in the desired state' { + Context 'When the IP address group is IPAll' { + Context 'When the IP address group is using dynamic port' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + Enabled = $false + IPAddress = $null + UseTcpDynamicPort = $true + TcpPort = $null + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + UseTcpDynamicPort = $true + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'UseTcpDynamicPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeTrue + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group is using static TCP ports' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + Enabled = $false + IPAddress = $null + UseTcpDynamicPort = $false + TcpPort = '1433' + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + TcpPort = '1433' + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TcpPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be '1433' + $comparedReturnValue.Actual | Should -Be '1433' + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the IP address group is IPx (where x is an available group number)' { + Context 'When the IP address group is using dynamic port' { + BeforeAll { + $mockIpAddress = 'fe80::7894:a6b6:59dd:c8ff%9' + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $true + IPAddress = $mockIpAddress + UseTcpDynamicPort = $true + TcpPort = $null + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + UseTcpDynamicPort = $true + Enabled = $true + IPAddress = $mockIpAddress + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'UseTcpDynamicPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeTrue + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Enabled' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeTrue + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'IPAddress' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be $mockIpAddress + $comparedReturnValue.Actual | Should -Be $mockIpAddress + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group is using static TCP ports' { + BeforeAll { + $mockIpAddress = 'fe80::7894:a6b6:59dd:c8ff%9' + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $true + IPAddress = $mockIpAddress + UseTcpDynamicPort = $false + TcpPort = '1433' + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + TcpPort = '1433' + Enabled = $true + IPAddress = $mockIpAddress + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TcpPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be '1433' + $comparedReturnValue.Actual | Should -Be '1433' + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Enabled' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeTrue + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'IPAddress' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be $mockIpAddress + $comparedReturnValue.Actual | Should -Be $mockIpAddress + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the IP address group is IPAll' { + Context 'When the IP address group should be using dynamic port' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + Enabled = $false + IPAddress = $null + UseTcpDynamicPort = $false + TcpPort = '1433' + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + UseTcpDynamicPort = $true + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'UseTcpDynamicPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeFalse + $comparedReturnValue.InDesiredState | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group should be using static TCP ports' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + Enabled = $false + IPAddress = $null + UseTcpDynamicPort = $true + TcpPort = $null + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + TcpPort = '1433' + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TcpPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be '1433' + $comparedReturnValue.Actual | Should -BeNullOrEmpty + $comparedReturnValue.InDesiredState | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the IP address group is IPx (where x is an available group number)' { + Context 'When the IP address group should be using dynamic port' { + BeforeAll { + $mockIpAddress = 'fe80::7894:a6b6:59dd:c8ff%9' + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $true + IPAddress = $mockIpAddress + UseTcpDynamicPort = $false + TcpPort = '1433' + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + UseTcpDynamicPort = $true + Enabled = $true + IPAddress = $mockIpAddress + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'UseTcpDynamicPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeFalse + $comparedReturnValue.InDesiredState | Should -BeFalse + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Enabled' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeTrue + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'IPAddress' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be $mockIpAddress + $comparedReturnValue.Actual | Should -Be $mockIpAddress + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group should be using static TCP ports' { + BeforeAll { + $mockIpAddress = 'fe80::7894:a6b6:59dd:c8ff%9' + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $true + IPAddress = $mockIpAddress + UseTcpDynamicPort = $true + TcpPort = $null + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + TcpPort = '1433' + Enabled = $true + IPAddress = $mockIpAddress + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TcpPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be '1433' + $comparedReturnValue.Actual | Should -BeNullOrEmpty + $comparedReturnValue.InDesiredState | Should -BeFalse + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Enabled' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeTrue + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'IPAddress' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be $mockIpAddress + $comparedReturnValue.Actual | Should -Be $mockIpAddress + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group has the wrong IP adress' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $true + IPAddress = '10.0.0.1' + UseTcpDynamicPort = $false + TcpPort = '1433' + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + TcpPort = '1433' + Enabled = $true + IPAddress = 'fe80::7894:a6b6:59dd:c8ff%9' + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TcpPort' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be '1433' + $comparedReturnValue.Actual | Should -Be '1433' + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Enabled' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeTrue + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'IPAddress' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'fe80::7894:a6b6:59dd:c8ff%9' + $comparedReturnValue.Actual | Should -Be '10.0.0.1' + $comparedReturnValue.InDesiredState | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group has the wrong state for Enabled' { + BeforeAll { + $mockIpAddress = '10.0.0.1' + + Mock -CommandName Get-TargetResource -MockWith { + return @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $false + IPAddress = $mockIpAddress + UseTcpDynamicPort = $false + TcpPort = '1433' + } + } + + $compareTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $true + } + } + + It 'Should return the correct metadata for each protocol property' { + $compareTargetResourceStateResult = Compare-TargetResourceState @compareTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Enabled' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -BeTrue + $comparedReturnValue.Actual | Should -BeFalse + $comparedReturnValue.InDesiredState | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + } + } + + Describe 'SqlServerProtocolTcpIp\Set-TargetResource' -Tag 'Set' { + BeforeAll { + $mockInstanceName = 'DSCTEST' + } + + Context 'When the SQL Server instance does not exist' { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + InDesiredState = $false + } + ) + } + + BeforeAll { + Mock -CommandName Get-ServerProtocolObject -MockWith { + return $null + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + } + } + + It 'Should throw the correct error' { + $expectedErrorMessage = $script:localizedData.FailedToGetSqlServerProtocol + + { Set-TargetResource @setTargetResourceParameters } | Should -Throw $expectedErrorMessage + } + } + + Context 'When the IP address group is missing' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + InDesiredState = $false + } + ) + } + + Mock -CommandName Get-ServerProtocolObject -MockWith { + return @{ + IPAddresses = @( + [PSCustomObject] @{ + Name = 'IPAll' + } + [PSCustomObject] @{ + Name = 'IP1' + } + ) + } + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP2' + } + } + + It 'Should throw the correct error' { + $expectedErrorMessage = $script:localizedData.SetMissingIpAddressGroup -f 'IP2' + + { Set-TargetResource @setTargetResourceParameters } | Should -Throw $expectedErrorMessage + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + InDesiredState = $true + } + ) + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + } + } + + It 'Should return $true' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Compare-TargetResourceState -Exactly -Times 1 -Scope It + } + } + + Context 'When the system is not in the desired state' { + Context 'When the IP address group should be using dynamic port' { + BeforeAll { + Mock -CommandName Restart-SqlService + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'UseTcpDynamicPort' + Actual = $false + Expected = $true + InDesiredState = $false + } + ) + } + + Mock -CommandName Get-ServerProtocolObject -MockWith { + return New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddresses' -Value @{ + Name = 'IPAll' + IPAll = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddressProperties' -Value @{ + TcpPort = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value '1433' -PassThru -Force + TcpDynamicPorts = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value '' -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + <# + Verifies that the correct value was set by the test, + and to verify that the method Alter() is actually called. + #> + $ipAddressProperties = $this.IPAddresses['IPAll'].IPAddressProperties + + if ($ipAddressProperties['TcpDynamicPorts'].Value -eq '0' ` + -and $ipAddressProperties['TcpPort'].Value -eq '') + { + $script:wasMethodAlterCalled = $true + } + } -PassThru -Force + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + UseTcpDynamicPort = $true + } + } + + BeforeEach { + $script:wasMethodAlterCalled = $false + } + + It 'Should set the desired values and restart the SQL Server service' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + <# + Addition evaluation is done in the mock to test if the + object is set correctly. + #> + $script:wasMethodAlterCalled | Should -BeTrue + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group should be using static port' { + BeforeAll { + Mock -CommandName Restart-SqlService + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'TcpPort' + Actual = '' + Expected = '1433,1500,1501' + InDesiredState = $false + } + ) + } + + Mock -CommandName Get-ServerProtocolObject -MockWith { + return New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddresses' -Value @{ + Name = 'IPAll' + IPAll = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddressProperties' -Value @{ + TcpPort = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value '' -PassThru -Force + TcpDynamicPorts = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value '50000' -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + <# + Verifies that the correct value was set by the test, + and to verify that the method Alter() is actually called. + #> + $ipAddressProperties = $this.IPAddresses['IPAll'].IPAddressProperties + + if ($ipAddressProperties['TcpDynamicPorts'].Value -eq '' ` + -and $ipAddressProperties['TcpPort'].Value -eq '1433,1500,1501') + { + $script:wasMethodAlterCalled = $true + } + } -PassThru -Force + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IPAll' + TcpPort = '1433,1500,1501' + } + } + + BeforeEach { + $script:wasMethodAlterCalled = $false + } + + It 'Should set the desired values and restart the SQL Server service' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + <# + Addition evaluation is done in the mock to test if the + object is set correctly. + #> + $script:wasMethodAlterCalled | Should -BeTrue + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group is IPx (where x is an available group number)' { + Context 'When the IP address group should be enabled' { + BeforeAll { + Mock -CommandName Restart-SqlService + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Enabled' + Actual = $false + Expected = $true + InDesiredState = $false + } + ) + } + + Mock -CommandName Get-ServerProtocolObject -MockWith { + return New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddresses' -Value @{ + Name = 'IP1' + IP1 = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddressProperties' -Value @{ + Enabled = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value $false -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + <# + Verifies that the correct value was set by the test, + and to verify that the method Alter() is actually called. + #> + $ipAddressProperties = $this.IPAddresses['IP1'].IPAddressProperties + + if ($ipAddressProperties['Enabled'].Value -eq $true) + { + $script:wasMethodAlterCalled = $true + } + } -PassThru -Force + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $true + } + } + + BeforeEach { + $script:wasMethodAlterCalled = $false + } + + It 'Should set the desired values and restart the SQL Server service' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + <# + Addition evaluation is done in the mock to test if the + object is set correctly. + #> + $script:wasMethodAlterCalled | Should -BeTrue + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group should be disabled' { + BeforeAll { + Mock -CommandName Restart-SqlService + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Enabled' + Actual = $true + Expected = $false + InDesiredState = $false + } + ) + } + + Mock -CommandName Get-ServerProtocolObject -MockWith { + return New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddresses' -Value @{ + Name = 'IP1' + IP1 = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddressProperties' -Value @{ + Enabled = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value $true -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + <# + Verifies that the correct value was set by the test, + and to verify that the method Alter() is actually called. + #> + $ipAddressProperties = $this.IPAddresses['IP1'].IPAddressProperties + + if ($ipAddressProperties['Enabled'].Value -eq $false) + { + $script:wasMethodAlterCalled = $true + } + } -PassThru -Force + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $false + } + } + + BeforeEach { + $script:wasMethodAlterCalled = $false + } + + It 'Should set the desired values and restart the SQL Server service' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + <# + Addition evaluation is done in the mock to test if the + object is set correctly. + #> + $script:wasMethodAlterCalled | Should -BeTrue + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 1 -Scope It + } + } + + Context 'When the IP address group have the wrong IP adress' { + BeforeAll { + $mockExpectedIpAddress = 'fe80::7894:a6b6:59dd:c8ff%9' + Mock -CommandName Restart-SqlService + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'IpAddress' + Actual = '10.0.0.1' + Expected = $mockExpectedIpAddress + InDesiredState = $false + } + ) + } + + Mock -CommandName Get-ServerProtocolObject -MockWith { + return New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddresses' -Value @{ + Name = 'IP1' + IP1 = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddressProperties' -Value @{ + IpAddress = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value '10.0.0.1' -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value { + <# + Verifies that the correct value was set by the test, + and to verify that the method Alter() is actually called. + #> + $ipAddressProperties = $this.IPAddresses['IP1'].IPAddressProperties + + if ($ipAddressProperties['IpAddress'].Value -eq $mockExpectedIpAddress) + { + $script:wasMethodAlterCalled = $true + } + } -PassThru -Force + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + IpAddress = $mockExpectedIpAddress + } + } + + BeforeEach { + $script:wasMethodAlterCalled = $false + } + + It 'Should set the desired values and restart the SQL Server service' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + <# + Addition evaluation is done in the mock to test if the + object is set correctly. + #> + $script:wasMethodAlterCalled | Should -BeTrue + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 1 -Scope It + } + } + + Context 'When the restart should be suppressed' { + BeforeAll { + Mock -CommandName Write-Warning + Mock -CommandName Restart-SqlService + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Enabled' + Actual = $true + Expected = $false + InDesiredState = $false + } + ) + } + + Mock -CommandName Get-ServerProtocolObject -MockWith { + return New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddresses' -Value @{ + Name = 'IP1' + IP1 = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'IPAddressProperties' -Value @{ + Enabled = New-Object -TypeName PSObject | + Add-Member -MemberType NoteProperty -Name 'Value' -Value $true -PassThru -Force + } -PassThru -Force + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'Alter' -Value {} -PassThru -Force + } + + $setTargetResourceParameters = @{ + InstanceName = $mockInstanceName + IpAddressGroup = 'IP1' + Enabled = $false + SuppressRestart = $true + } + } + + It 'Should set the desired values and restart the SQL Server service' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + Assert-MockCalled -CommandName Restart-SqlService -Exactly -Times 0 -Scope It + Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 -Scope It + } + } + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/tests/Unit/SqlServerDsc.Common.Tests.ps1 b/tests/Unit/SqlServerDsc.Common.Tests.ps1 index a55670b48..7b19f1952 100644 --- a/tests/Unit/SqlServerDsc.Common.Tests.ps1 +++ b/tests/Unit/SqlServerDsc.Common.Tests.ps1 @@ -3582,4 +3582,86 @@ InModuleScope $script:subModuleName { } } } + + Describe 'SqlServerProtocol\Get-ProtocolNameProperties' { + $testCases = @( + @{ + ParameterValue = 'TcpIp' + DisplayName = 'TCP/IP' + Name = 'Tcp' + } + @{ + ParameterValue = 'NamedPipes' + DisplayName = 'Named Pipes' + Name = 'Np' + } + @{ + ParameterValue = 'SharedMemory' + DisplayName = 'Shared Memory' + Name = 'Sm' + } + ) + + It 'Should return the correct values when the protocol is ''''' -TestCases $testCases { + param + ( + [Parameter()] + [System.String] + $ParameterValue, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Name + ) + + $result = Get-ProtocolNameProperties -ProtocolName $ParameterValue + + $result.DisplayName = $DisplayName + $result.Name = $Name + } + } + + Describe 'SqlServerProtocol\Get-ServerProtocolObject' { + BeforeAll { + $mockInstanceName = 'TestInstance' + + Mock -CommandName New-Object -MockWith { + return @{ + ServerInstances = @{ + $mockInstanceName = @{ + ServerProtocols = @{ + Tcp = @{ + IsEnabled = $true + HasMultiIPAddresses = $true + ProtocolProperties = @{ + ListenOnAllIPs = $true + KeepAlive = 30000 + } + } + } + } + } + } + } + } + + It 'Should return a ManagedComputer object with the correct values' { + $mockGetServerProtocolObjectParameters = @{ + ServerName = 'AnyServer' + Instance = $mockInstanceName + ProtocolName = 'TcpIp' + } + + $result = Get-ServerProtocolObject @mockGetServerProtocolObjectParameters + + $result.IsEnabled | Should -BeTrue + $result.HasMultiIPAddresses | Should -BeTrue + $result.ProtocolProperties.ListenOnAllIPs | Should -BeTrue + $result.ProtocolProperties.KeepAlive | Should -Be 30000 + } + } }