DSCResources/MicrosoftAzure_xSqlAvailabilityGroupListener/MicrosoftAzure_xSqlAvailabilityGroupListener.psm1
# # xSqlAvailabilityGroupListener: DSC resource that configures a SQL AlwaysOn Availability Group Listener. # function Get-TargetResource { param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $Name, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $AvailabilityGroupName, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $DomainNameFqdn, [String] $ListenerIPAddress, [UInt32] $ListenerPortNumber = 1433, [UInt32] $ProbePortNumber = 59999, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $InstanceName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $DomainCredential, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $SqlAdministratorCredential ) $bConfigured = Test-TargetResource -Name $Name -AvailabilityGroupName $AvailabilityGroupName -DomainNameFqdn $DomainNameFqdn -ListenerPortNumber $ListenerPortNumber -ProbePortNumber $ProbePortNumber -InstanceName $InstanceName -DomainCredential $DomainCredential -SqlAdministratorCredential $SqlAdministratorCredential $returnValue = @{ Name = $Name AvailabilityGroupName = $AvailabilityGroupName DomainNameFqdn = $DomainNameFqdn ListenerPortNumber = $ListenerPortNumber ProbePortNumber = $ProbePortNumber InstanceName = $InstanceName DomainCredential = $DomainCredential.UserName SqlAdministratorCredential = $SqlAdministratorCredential.UserName Configured = $bConfigured } $returnValue } function Set-TargetResource { param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $Name, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $AvailabilityGroupName, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $DomainNameFqdn, [String] $ListenerIPAddress, [UInt32] $ListenerPortNumber = 1433, [UInt32] $ProbePortNumber = 59999, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $InstanceName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $DomainCredential, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $SqlAdministratorCredential ) $instance = Get-SqlInstanceName -Node $env:COMPUTERNAME -InstanceName $InstanceName $s = Get-SqlServer -InstanceName $instance -Credential $SqlAdministratorCredential $Stoploop = $false $Retrycount = 0 if ($ListenerIPAddress) { Write-Verbose -Message "The assigned Listener public IP address is '$($ListenerIPAddress)'" $publicIpAddress = $ListenerIPAddress } else { do { try { $publicIpAddress = ([System.Net.DNS]::GetHostAddresses($DomainNameFqdn)).IPAddressToString Write-Verbose -Message "Got Listener public IP address ..'$($publicIpAddress)'" $Stoploop = $true } catch { if ($Retrycount -gt 20){ Write-Host "Could not Get Host Addresses after 20 retrys." $Stoploop = $true } else { Write-Host "Could not Get Host Addresses retrying in 60 seconds..." Start-Sleep -Seconds 60 $Retrycount = $Retrycount + 1 } } } While ($Stoploop -eq $false) } try { ($oldToken, $context, $newToken) = ImpersonateAs -cred $DomainCredential Write-Verbose -Message "Stopping cluster resource '$($AvailabilityGroupName)' ..." Stop-ClusterResource -Name $AvailabilityGroupName -ErrorAction SilentlyContinue | Out-Null if (!(Get-ClusterResource $Name -ErrorAction Ignore)) { Write-Verbose -Message "Creating Network Name resource '$($Name)' ..." $params= @{ Name = $Name DnsName = $Name } Add-ClusterResource -Name $Name -ResourceType "Network Name" -Group $AvailabilityGroupName -ErrorAction Stop | Set-ClusterParameter -Multiple $params -ErrorAction Stop Write-Verbose -Message "Setting resource dependency between '$($AvailabilityGroupName)' and '$($Name)' ..." Get-ClusterResource -Name $AvailabilityGroupName | Set-ClusterResourceDependency "[$Name]" -ErrorAction Stop } if (!(Get-ClusterResource "IP Address $publicIpAddress" -ErrorAction Ignore)) { Write-Verbose -Message "Creating IP Address resource for '$($publicIpAddress)' ..." $params = @{ Address = $publicIpAddress ProbePort = $ProbePortNumber SubnetMask = "255.255.255.255" Network = (Get-ClusterNetwork)[0].Name OverrideAddressMatch = 1 EnableDhcp = 0 } Add-ClusterResource -Name "IP Address $publicIpAddress" -ResourceType "IP Address" -Group $AvailabilityGroupName -ErrorAction Stop | Set-ClusterParameter -Multiple $params -ErrorAction Stop Write-Verbose -Message "Setting resource dependency between '$($Name)' and '$($publicIpAddress)' ..." Get-ClusterResource -Name $Name | Set-ClusterResourceDependency "[IP Address $publicIpAddress]" -ErrorAction Stop } Write-Verbose -Message "Starting cluster resource '$($Name)' ..." Start-ClusterResource -Name $Name -ErrorAction Stop | Out-Null Write-Verbose -Message "Starting cluster resource '$($AvailabilityGroupName)' ..." Start-ClusterResource -Name $AvailabilityGroupName -ErrorAction Stop | Out-Null } finally { if ($context) { $context.Undo() $context.Dispose() CloseUserToken($newToken) } } Write-Verbose -Message "Setting the Availability Group Listener port to '$($ListenerPortNumber)' ..." $ag = Get-SqlAvailabilityGroup -Name $AvailabilityGroupName -Server $s $agListener = $ag.AvailabilityGroupListeners | where { $_.Name -eq $Name } $agListener.PortNumber = $ListenerPortNumber $agListener.Alter() } function Test-TargetResource { param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $Name, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $AvailabilityGroupName, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $DomainNameFqdn, [String] $ListenerIPAddress, [UInt32] $ListenerPortNumber = 1433, [UInt32] $ProbePortNumber = 59999, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $InstanceName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $DomainCredential, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $SqlAdministratorCredential ) Write-Verbose -Message "Checking if SQL AG '$($AvailabilityGroupName)' exists on instance '$($InstanceName)' ..." $instance = Get-SqlInstanceName -Node $env:COMPUTERNAME -InstanceName $InstanceName $s = Get-SqlServer -InstanceName $instance -Credential $SqlAdministratorCredential $ag = Get-SqlAvailabilityGroup -Name $AvailabilityGroupName -Server $s if ($ag) { Write-Verbose -Message "SQL AG '$($AvailabilityGroupName)' found." } else { throw "SQL GA '$($AvailabilityGroupName)' NOT found." } try { ($oldToken, $context, $newToken) = ImpersonateAs -cred $DomainCredential Write-Verbose -Message "Checking if Network Name resource '$($Name)' exists ..." if (Get-ClusterResource $Name -ErrorAction Ignore) { Write-Verbose -Message "Network Name resource '$($Name)' found." } else { Write-Verbose -Message "Network Name resource '$($Name)' NOT found." return $false } $publicIpAddress = ([System.Net.DNS]::GetHostAddresses($DomainNameFqdn)).IPAddressToString Write-Verbose -Message "Checking if IP Address resource for '$($publicIpAddress)' exists ..." if (Get-ClusterResource "IP Address $publicIpAddress" -ErrorAction Ignore) { Write-Verbose -Message "IP Address resource 'IP Address $($publicIpAddress)' found." } else { Write-Verbose -Message "IP Address resource 'IP Address $($publicIpAddress)' NOT found." return $false } } finally { if ($context) { $context.Undo() $context.Dispose() CloseUserToken($newToken) } } return $true } function Get-SqlServer([string]$InstanceName, [PSCredential]$Credential) { [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null $sc = New-Object Microsoft.SqlServer.Management.Common.ServerConnection $sc.ServerInstance = $InstanceName $sc.ConnectAsUser = $true if ($Credential.GetNetworkCredential().Domain -and $Credential.GetNetworkCredential().Domain -ne $env:COMPUTERNAME) { $sc.ConnectAsUserName = "$($Credential.GetNetworkCredential().UserName)@$($Credential.GetNetworkCredential().Domain)" } else { $sc.ConnectAsUserName = $Credential.GetNetworkCredential().UserName } $sc.ConnectAsUserPassword = $Credential.GetNetworkCredential().Password [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null $s = New-Object Microsoft.SqlServer.Management.Smo.Server $sc $s } function Get-SqlInstanceName([string]$Node, [string]$InstanceName) { $pureInstanceName = Get-PureSqlInstanceName -InstanceName $InstanceName if ("MSSQLSERVER" -eq $pureInstanceName) { $Node } else { $Node + "\" + $pureInstanceName } } function Get-PureSqlInstanceName([string]$InstanceName) { $list = $InstanceName.Split("\") if ($list.Count -gt 1) { $list[1] } else { "MSSQLSERVER" } } function Get-SqlAvailabilityGroup([string]$Name, [Microsoft.SqlServer.Management.Smo.Server]$Server) { $s.AvailabilityGroups | where { $_.Name -eq $Name } } function Get-ImpersonateLib { if ($script:ImpersonateLib) { return $script:ImpersonateLib } $sig = @' [DllImport("advapi32.dll", SetLastError = true)] public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("kernel32.dll")] public static extern Boolean CloseHandle(IntPtr hObject); '@ $script:ImpersonateLib = Add-Type -PassThru -Namespace 'Lib.Impersonation' -Name ImpersonationLib -MemberDefinition $sig return $script:ImpersonateLib } function ImpersonateAs([PSCredential] $cred) { [IntPtr] $userToken = [Security.Principal.WindowsIdentity]::GetCurrent().Token $userToken $ImpersonateLib = Get-ImpersonateLib $bLogin = $ImpersonateLib::LogonUser($cred.GetNetworkCredential().UserName, $cred.GetNetworkCredential().Domain, $cred.GetNetworkCredential().Password, 9, 0, [ref]$userToken) if ($bLogin) { $Identity = New-Object Security.Principal.WindowsIdentity $userToken $context = $Identity.Impersonate() } else { throw "Can't log on as user '$($cred.GetNetworkCredential().UserName)'." } $context, $userToken } function CloseUserToken([IntPtr] $token) { $ImpersonateLib = Get-ImpersonateLib $bLogin = $ImpersonateLib::CloseHandle($token) if (!$bLogin) { throw "Can't close token." } } Export-ModuleMember -Function *-TargetResource |