SqlServerDsc
SqlServerDsc copied to clipboard
SqlAGListener: Error causing Listener Port number to be deleted.
Details of the scenario you tried and the problem that is occurring
When running my DSC Script for configuring AGs for SQL Server I started receiving an error on Servers that already had the listener configured.
When I check the SQL Server through management studio the listener no longer had the default 1433 port number listed.

Here is a screenshot of the SQLAGListener Section of my configuration script.

Verbose logs showing the problem
Suggested solution to the issue
The DSC configuration that is used to reproduce the issue (as detailed as possible)
Configuration SQLAG
{
param(
[Parameter(Mandatory = $true)]
[System.Management.Automation.PSCredential]
$SqlAdministratorCredential
)
Import-DscResource -ModuleName SqlServerDsc
Node $AllNodes.NodeName
{
# Adding the required service account to allow the cluster to log into SQL
SqlServerLogin AddNTServiceClusSvc
{
Ensure = 'Present'
Name = 'NT SERVICE\ClusSvc'
LoginType = 'WindowsUser'
ServerName = $Node.NodeName
InstanceName = $Node.InstanceName
PsDscRunAsCredential = $SqlAdministratorCredential
}
# Add the required permissions to the cluster service login
SqlServerPermission AddNTServiceClusSvcPermissions
{
DependsOn = '[SqlServerLogin]AddNTServiceClusSvc'
Ensure = 'Present'
ServerName = $Node.NodeName
InstanceName = $Node.InstanceName
Principal = 'NT SERVICE\ClusSvc'
Permission = 'AlterAnyAvailabilityGroup', 'ViewServerState', 'ConnectSQL'
PsDscRunAsCredential = $SqlAdministratorCredential
}
WindowsFeature FailoverFeature
{
Ensure = "Present"
Name = "Failover-Clustering"
DependsOn ='[SqlServerPermission]AddNTServiceClusSvcPermissions'
}
WindowsFeature RSATCLUSTERINGPOWERSHELL
{
Ensure = "Present"
Name = "RSAT-Clustering-Powershell"
DependsOn ='[WindowsFeature]FailoverFeature'
}
WindowsFeature RSATCLUSTERINGMGMT
{
Ensure = "Present"
Name = "RSAT-Clustering-Mgmt"
DependsOn ='[WindowsFeature]FailoverFeature'
}
WindowsFeature RSATCLUSTERINGCMDINTERFACE
{
Ensure = "Present"
Name = "RSAT-Clustering-CmdInterface"
DependsOn ='[WindowsFeature]RSATCLUSTERINGPOWERSHELL'
}
# Create a DatabaseMirroring endpoint
SqlServerEndpoint HADREndpoint
{
EndPointName = 'HADR'
Ensure = 'Present'
Port = 5022
ServerName = $Node.NodeName
InstanceName = $Node.InstanceName
PsDscRunAsCredential = $SqlAdministratorCredential
}
SqlAlwaysOnService EnableHADR
{
Ensure = 'Present'
ServerName = $Node.NodeName
InstanceName = $Node.InstanceName
PsDscRunAsCredential = $SqlAdministratorCredential
}
if ( $Node.Role -eq 'PrimaryReplica' )
{
# Create the availability group on the instance tagged as the primary replica
SqlAG AddTestAG
{
Ensure = 'Present'
Name = $Node.AvailabilityGroupName
InstanceName = $Node.InstanceName
ServerName = $Node.NodeName
FailoverMode = $Node.FailoverMode
AutomatedBackupPreference = 'Primary'
DatabaseHealthTrigger = $TRUE
AvailabilityMode ='SynchronousCommit'
DependsOn = '[SqlAlwaysOnService]EnableHADR', '[SqlServerEndpoint]HADREndpoint', '[SqlServerPermission]AddNTServiceClusSvcPermissions'
PsDscRunAsCredential = $SqlAdministratorCredential
}
SqlAGListener AvailabilityGroupListenerWithSameNameAsVCO
{
Ensure = 'Present'
Port = 1433
ServerName = $Node.NodeName
InstanceName = 'MSSQLSERVER'
AvailabilityGroup = $Node.AvailabilityGroupName
Name = $Node.AvailabilityGroupName
IpAddress = $Node.ListenerIP
PsDscRunAsCredential = $SqlAdministratorCredential
}
}
if ( $Node.Role -eq 'SecondaryReplica' )
{
# Add the availability group replica to the availability group
SqlWaitForAG SQLConfigureAG-WaitAGTest1
{
Name = $Node.AvailabilityGroupName
RetryIntervalSec = 15
RetryCount = 2
PsDscRunAsCredential = $SqlAdministratorCredential
}
SqlAGReplica AddReplica
{
Ensure = 'Present'
Name = $Node.NodeName
AvailabilityGroupName = $Node.AvailabilityGroupName
FailoverMode = $Node.FailoverMode
AvailabilityMode = $Node.AvailabilityMode
ServerName = $Node.NodeName
InstanceName = 'MSSQLSERVER'
ReadOnlyRoutingList = $Node.ReadOnlyRoutingList
PrimaryReplicaServerName = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' -and $_.AvailabilityGroupName -eq $Node.AvailabilityGroupName } ).NodeName
PrimaryReplicaInstanceName = ( $AllNodes | Where-Object { $_.Role -eq 'PrimaryReplica' -and $_.AvailabilityGroupName -eq $Node.AvailabilityGroupName } ).InstanceName
DependsOn = '[SqlAlwaysOnService]EnableHADR'
ProcessOnlyOnActiveNode = $Node.ProcessOnlyOnActiveNode
PsDscRunAsCredential = $SqlAdministratorCredential
}
}
}
}
SQL Server edition and version the target node is running
Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64) Nov 30 2018 12:57:58 Copyright (C) 2017 Microsoft Corporation Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2016 Standard 10.0 <X64> (Build 14393: ) (Hypervisor)
SQL Server PowerShell modules present on the target node
Name Version Path
sqlserver 21.0.17240 C:\Program Files\WindowsPowerShell\Modules\sqlserver\21.0.17240\sqlserver.psd1
SQLPS 14.0 C:\Program Files (x86)\Microsoft SQL Server\140\Tools\PowerShell\Modules\SQLPS\SQLPS.psd1
The operating system the target node is running
OsName : Microsoft Windows Server 2016 Standard OsOperatingSystemSKU : StandardServerEdition OsArchitecture : 64-bit WindowsBuildLabEx : 14393.2758.amd64fre.rs1_release_1.190104-1904 OsLanguage : en-US OsMuiLanguages : {en-US}
Version and build of PowerShell the target node is running
Name Value
PSVersion 5.1.14393.2758
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.14393.2758
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
Version of the DSC module that was used ('dev' if using current dev branch)
SqlServerDsc 12.0.0.0
Hmm. Was the port number set to -1, and the Get-TargetReosurce failed? 🤔 Labeling this as a bug.
I didn't set to -1, and the GUI in management studio doesn't show it as being that. It's very strange because it seems to work every other time. However, once it deletes the port number it will not change it back no matter how many times you run it.
Can you still reproduce the error? If so I could gather some code for you to run manually to see what the values actually are (what is reported by the SMO object). Would be great to have that to be able to build a regression test so we know we fixed the issue, and that it doesn’t happen again.
Yeah sure I can do that.
In a new PowerShell session on the server reporting the issue, please run this. Try running this, change the strings to the proper names.
Import-Module SqlServer
$sqlServerObject = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList 'servername\instancename'
$availabilityGroupObject = $sqlServerObject.AvailabilityGroups['MyAgGroupName']
$availabilityGroupListener = $availabilityGroupObject.AvailabilityGroupListeners['MyListenerName']
$availabilityGroupListener | Select-Object -ExpandProperty PortNumber
$port = [uint16]( $availabilityGroupListener | Select-Object -ExpandProperty PortNumber )
$port
$presentIpAddress = $availabilityGroupListener.AvailabilityGroupListenerIPAddresses
$presentIpAddress
$presentIpAddress | Select-Object -First 1 -ExpandProperty IsDHCP
$dhcp = [bool]( $presentIpAddress | Select-Object -First 1 -ExpandProperty IsDHCP )
$dhcp
The port number looks like it was set to -1. A similar error was thrown when trying to convert it to [uint16]
Yep, unsigned integer cannot be negative. Thanks for confirming that! We either need to change the type to [int16] or just return the port number as 0 if it is -1. Not sure what -1 means, and what it means for the listener if it is set.
Wonder what caused the port number to be removed 🤔 I guess -1 means port number is not yet set or has not been set yet? :thinking:
Interesting..I would assume -1 does mean it has not been set. However, connections appear to still work even after it clears the port number. Will this just be fixed in the next release?
Anyone in the community can send in a PR to resolve this issue. Do you want to give it a try? I will happily review a PR for this change.
Though we should add a unit test to regression test this issue. So we need to a unit test that give the same error as above, and then solve the issue so that the unit test passes.
Yeah I don't mind sending in a PR. It looks like changing to a [int16] did the trick. How do I go about setting up the unit test?
Since it is the PortNumber we need to regression test, we need to mock the Get-SQLAlwaysOnAvailabilityGroupListener function to return a the -1 for the port number.
https://github.com/PowerShell/SqlServerDsc/blob/7209a03e43f7512dea90c562095bbf99789a43dd/DSCResources/MSFT_SqlAGListener/MSFT_SqlAGListener.psm1#L64
I though I would tell how to write the mock and test using permalinks pointing to existing tests. But the unit tests for the Get-TargetResource is in need for a refactor since it makes second-level call mocks (mocking function calls inside functions the Get-TargetResource is calling). So I made a unit test that mocks the first-level call, to the Get-SQLAlwaysOnAvailabilityGroupListener function.
If you are not familiar with Pester this shows a more correct way of building this unit test.
Add the following unit test (Context-block) somewhere inside the 'Get-TargetResource' Describe-block.
Context 'When the system is in the desired state' {
# Regression test for issue #1360
Context 'When the availability group exist, but the PortNumber is returned as -1' {
BeforeAll {
Mock -CommandName Get-SQLAlwaysOnAvailabilityGroupListener -MockWith {
return New-Object -TypeName Object |
Add-Member -MemberType NoteProperty -Name PortNumber -Value -1 -PassThru |
Add-Member -MemberType ScriptProperty -Name AvailabilityGroupListenerIPAddresses -Value {
return @(
# TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddressCollection
(New-Object -TypeName Object | # TypeName: Microsoft.SqlServer.Management.Smo.AvailabilityGroupListenerIPAddress
Add-Member -MemberType NoteProperty -Name IsDHCP -Value $false -PassThru |
Add-Member -MemberType NoteProperty -Name IPAddress -Value '192.168.0.1' -PassThru |
Add-Member -MemberType NoteProperty -Name SubnetMask -Value '255.255.255.0' -PassThru
)
)
} -PassThru -Force
}
$testParameters = $defaultParameters.Clone()
}
It 'Should return the desired state as present' {
$result = Get-TargetResource @testParameters
$result.Ensure | Should -Be 'Present'
Assert-MockCalled Get-SQLAlwaysOnAvailabilityGroupListener -Exactly -Times 1 -Scope It
}
It 'Should return the same values as passed as parameters' {
$result = Get-TargetResource @testParameters
$result.ServerName | Should -Be $testParameters.ServerName
$result.InstanceName | Should -Be $testParameters.InstanceName
$result.Name | Should -Be $testParameters.Name
$result.AvailabilityGroup | Should -Be $testParameters.AvailabilityGroup
}
It 'Should return the correct values for the rest of the properties' {
$result = Get-TargetResource @testParameters
$result.IpAddress | Should -Be '192.168.0.1/255.255.255.0'
$result.Port | Should -Be -1
$result.DHCP | Should -Be $false
}
}
}
Run the tests using the following, and the test should throw the same error as described here. Once the error is resolved, the test should pass (this is Test-Driven development by the way).
Invoke-Pester .\Tests\Unit\MSFT_SqlAGListener.Tests.ps1 -CodeCoverage .\DSCResources\MSFT_SqlAGListener\MSFT_SqlAGListener.psm1
You need to have the latest Pester version installed Install-Module Pester -Force
Sorry to interject...
Yeah I don't mind sending in a PR. It looks like changing to a [int16] did the trick. How do I go about setting up the unit test?
If you set it to int16, you effectively limit this to using half of the available ports.
Maximum possible port number is 65535. Maximum value for signed 16-bit integer is 32767.
The value should be changed to int32.
Chris