DSCResources/MicrosoftAzure_xSqlAvailabilityGroup/MicrosoftAzure_xSqlAvailabilityGroup.psm1
# # xSqlAvailabilityGroup: DSC resource to configure a SQL AlwaysOn Availability Group. # function Get-TargetResource { param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $Name, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $ClusterName, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $InstanceName, [ValidateRange(1000,9999)] [UInt32] $PortNumber = 5022, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $DomainCredential, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $SqlAdministratorCredential ) $bConfigured = Test-TargetResource -Name $Name -ClusterName $ClusterName -InstanceName $InstanceName -PortNumber $PortNumber -DomainCredential $DomainCredential -SqlAdministratorCredential $SqlAdministratorCredential $returnValue = @{ Name = $Name ClusterName = $ClusterName InstanceName = $InstanceName PortNumber = $PortNumber DomainCredential = $DomainCredential.UserName SqlAdministratorCredential = $SqlAdministratorCredential.UserName Configured = $bConfigured } $returnValue } function Set-TargetResource { param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $Name, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $ClusterName, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $InstanceName, [ValidateRange(1000,9999)] [UInt32] $PortNumber = 5022, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $DomainCredential, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $SqlAdministratorCredential ) $computerInfo = Get-WmiObject Win32_ComputerSystem if (($computerInfo -eq $null) -or ($computerInfo.Domain -eq $null)) { throw "Can't find node's domain name." } $domain = $ComputerInfo.Domain # Use the enumeration of cluster nodes as the replicas to add to the availability group. try { ($oldToken, $context, $newToken) = ImpersonateAs -cred $DomainCredential Write-Verbose -Message "Enumerating nodes in cluster '$($ClusterName)' ..." $nodes = Get-ClusterNode -Cluster $ClusterName Write-Verbose -Message "Found $(($nodes).Count) nodes." } finally { if ($context) { $context.Undo() $context.Dispose() CloseUserToken($newToken) } } # Find an existing availability group with the same name and look up its primary replica. Write-Verbose -Message "Checking if SQL AG '$($Name)' exists ..." $bAGExist = $false foreach ($node in $nodes.Name) { $instance = Get-SqlInstanceName -Node $node -InstanceName $InstanceName $s = Get-SqlServer -InstanceName $instance -Credential $SqlAdministratorCredential $group = Get-SqlAvailabilityGroup -Name $Name -Server $s if ($group) { Write-Verbose -Message "Found SQL AG '$($Name)' on instance '$($instance)'." $bAGExist = $true $primaryReplica = Get-SqlAvailabilityGroupPrimaryReplica -Name $Name -Server $s if ($primaryReplica -eq $env:COMPUTERNAME) { Write-Verbose -Message "Instance '$($instance)' is the primary replica in SQL AG '$($Name)'" } } } # Create the availability group and primary replica. if (!$bAGExist) { try { Write-Verbose -Message "Creating SQL AG '$($Name)' ..." $s = Get-SqlServer -InstanceName $InstanceName -Credential $SqlAdministratorCredential $instance = Get-SqlInstanceName -Node $env:COMPUTERNAME -InstanceName $InstanceName $newAG = New-Object -Type Microsoft.SqlServer.Management.Smo.AvailabilityGroup -ArgumentList $s,$Name $newAG.AutomatedBackupPreference = 'Secondary' $newPrimaryReplica = New-Object -Type Microsoft.SqlServer.Management.Smo.AvailabilityReplica -ArgumentList $newAG,$instance.ToUpperInvariant() $newPrimaryReplica.EndpointUrl = "TCP://$($s.NetName).$($domain):$PortNumber" $newPrimaryReplica.AvailabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::SynchronousCommit $newPrimaryReplica.FailoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::Automatic $newAG.AvailabilityReplicas.Add($newPrimaryReplica) $s.AvailabilityGroups.Add($newAG) $newAG.Create() $primaryReplica = $s.NetName } catch { Write-Error "Error creating availability group '$($Name)'." throw $_ } } # Create the secondary replicas and join them to the availability group. $nodeIndex = 2 foreach ($node in $nodes.Name) { if ($node -eq $primaryReplica) { continue } Write-Verbose -Message "Adding replica '$($node)' to SQL AG '$($Name)' ..." $instance = Get-SqlInstanceName -Node $node -InstanceName $InstanceName # Most operations are performed on the primary replica. $s = Get-SqlServer -InstanceName $primaryReplica -Credential $SqlAdministratorCredential $group = Get-SqlAvailabilityGroup -Name $Name -Server $s # Ensure the replica is not currently in the availability group. $localReplica = Get-SqlAvailabilityGroupReplicas -Name $Name -Server $s | where { $_.Name -eq $node } if ($localReplica) { Write-Verbose -Message "Found replica '$($node)' in SQL AG '$($Name)', removing ..." $localReplica.Drop() } # Automatic failover can be specified for up to two availability replicas. if ($nodeIndex -le 2) { $failoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::Automatic } else { $failoverMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaFailoverMode]::Manual } # Synchronous commit can be specified for up to three availability replicas. if ($nodeIndex -le 3) { $availabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::SynchronousCommit } else { $availabilityMode = [Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode]::AsynchronousCommit } # Add the replica to the availability group. $newReplica = New-Object -Type Microsoft.SqlServer.Management.Smo.AvailabilityReplica -ArgumentList $group,$instance.ToUpperInvariant() $newReplica.EndpointUrl = "TCP://$($node).$($domain):$PortNumber" $newReplica.AvailabilityMode = $availabilityMode $newReplica.FailoverMode = $failoverMode $group.AvailabilityReplicas.Add($newReplica) $newReplica.Create() $group.Alter() # Now join the replica to the availability group. $s = Get-SqlServer -InstanceName $instance -Credential $SqlAdministratorCredential $s.JoinAvailabilityGroup($group.Name) $s.Alter() } } function Test-TargetResource { param ( [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $Name, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $ClusterName, [parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String] $InstanceName, [ValidateRange(1000,9999)] [UInt32] $PortNumber = 5022, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $DomainCredential, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PSCredential] $SqlAdministratorCredential ) Write-Verbose -Message "Checking if SQL AG '$($Name)' exists on instance '$($InstanceName) ..." $instance = Get-SqlInstanceName -Node $node -InstanceName $InstanceName $s = Get-SqlServer -InstanceName $instance -Credential $SqlAdministratorCredential $group = Get-SqlAvailabilityGroup -Name $Name -Server $s if ($group) { Write-Verbose -Message "SQL AG '$($Name)' found." $true } else { Write-Verbose -Message "SQL AG '$($Name)' NOT found." $false } # TODO: add additional tests for AG membership, port, etc. } function Get-SqlAvailabilityGroup([string]$Name, [Microsoft.SqlServer.Management.Smo.Server]$Server) { $s.AvailabilityGroups | where { $_.Name -eq $Name } } function Get-SqlAvailabilityGroupPrimaryReplica([string]$Name, [Microsoft.SqlServer.Management.Smo.Server]$Server) { $s.AvailabilityGroups | where { $_.Name -eq $Name } | select -ExpandProperty 'PrimaryReplicaServerName' } function Get-SqlAvailabilityGroupReplicas([string]$Name, [Microsoft.SqlServer.Management.Smo.Server]$Server) { $s.AvailabilityGroups | where { $_.Name -eq $Name } | select -ExpandProperty 'AvailabilityReplicas' } 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-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 |