Finding Primary Replicas for SQL Server 2012 AlwaysOn Availability Groups with PowerShell

By:   |   Comments (6)   |   Related: > Availability Groups


Problem

We have numerous SQL 2012 Availability Groups in our environment with more projected to come online. The issue we are faced with is that we want to find which SQL Instance (cluster node) is hosting the Primary Replica. You can use SSMS to find the information either with the DashBoard or by expanding each Availability Group. This will show you the information but when you get over 2-3 Availability Groups then it can be problematic to have all the tabs in SSMS for each Dashboard or to have each instance open in SSMS. A better method is to turn to PowerShell to help us find a solution to the issue. We can use the system DMVs for Availability Groups to create a T-SQL query and then use PowerShell to iterate through a list of SQL Servers and query them in turn storing any results in a dataset object. We will use the .NET tool chest to connect to and run T-SQL against our servers returning results only for Servers in Availability Groups and filtering for Primary Replicas.

Solution

My solution uses PowerShell to execute a T-SQL script against a list of SQL instances and return the cluster node holding the Primary Replica for the Availability Group. It will run against SQL 2005 to SQL 2012 (not tested on SQL 2014, but no reason it would fail) however since we are focused on SQL 2012 and higher we should keep the list of instances to the versions supporting Availability Groups only. The list is embedded in the PowerShell but we could use a file or a SQL table to feed the SQL instances into the script to be iterated through. I will leave that to those who need it, the modification needed being changing the input method to place the instance list into a variable as the collection of objects to iterate through.

The parts we require are the T-SQL to find whether a cluster node's instance is holding a Primary Replica and the PowerShell to create a collection of SQL instance objects, connect to the instance, run the T-SQL code and return the results into a dataset. Lastly we print the dataset to the screen showing the Primary Replica for each Availability Group. We could upload the results to a SQL table or write to a file on disk, we could even email it, whatever the best method for dispensing the information to those who need it.

T-SQL to Find if the SQL Instance is a Primary Replica

We will use several system DMVs to create a T-SQL query that will determine if the local SQL instance on the cluster node is the Primary Replica for an Availability Group.

IF SERVERPROPERTY ('IsHadrEnabled') = 1
BEGIN
SELECT
   AGC.name -- Availability Group
 , RCS.replica_server_name -- SQL cluster node name
 , ARS.role_desc  -- Replica Role
 , AGL.dns_name  -- Listener Name
FROM
 sys.availability_groups_cluster AS AGC
  INNER JOIN sys.dm_hadr_availability_replica_cluster_states AS RCS
   ON
    RCS.group_id = AGC.group_id
  INNER JOIN sys.dm_hadr_availability_replica_states AS ARS
   ON
    ARS.replica_id = RCS.replica_id
  INNER JOIN sys.availability_group_listeners AS AGL
   ON
    AGL.group_id = ARS.group_id
WHERE
 ARS.role_desc = 'PRIMARY'
END

The results of the above query against a SQL instance which is the Primary Replica is shown below.

SSMS Single Instance Primary Query

That is all that we need to run on an instance to see if there are any Primary Replicas for a Availability Group on that instance. Since any Availability Group should be composed of two or more SQL instances (degenerate case of one node cluster ignored) we have to check all the SQL instances in the WSFC that make up the cluster hosting the Availability Group. PowerShell to the Rescue!

PowerShell script to iterate against a collection of SQL instances and run T-SQL code

The PowerShell script runs on version 2 and higher and does not use any add-ins or Providers just the .NET functionality in PowerShell itself. The part that is new to most people will be the .NET OLDEB functionality that uses the OleDbDataAdapter to run a T-SQL command and take the results and populate a dataset.

## Setup dataset to hold results
$dataset = New-Object System.Data.DataSet
## populate variable with collection of SQL instances
$serverlist='DBS04A','DBS04B','DBS05A','DBS05B','DBS06A','DBS06B'
## Setup connection to SQL server inside loop and run T-SQL against instance 
foreach($Server in $serverlist) {
$connectionString = "Provider=sqloledb;Data Source=$Server;Initial Catalog=Master;Integrated Security=SSPI;"
## place the T-SQL in variable to be executed by OLEDB method
$sqlcommand="
IF SERVERPROPERTY ('IsHadrEnabled') = 1
BEGIN
SELECT
   AGC.name
 , RCS.replica_server_name
 , ARS.role_desc
 , AGL.dns_name
FROM
 sys.availability_groups_cluster AS AGC
  INNER JOIN sys.dm_hadr_availability_replica_cluster_states AS RCS
   ON
    RCS.group_id = AGC.group_id
  INNER JOIN sys.dm_hadr_availability_replica_states AS ARS
   ON
    ARS.replica_id = RCS.replica_id
  INNER JOIN sys.availability_group_listeners AS AGL
   ON
    AGL.group_id = ARS.group_id
WHERE
 ARS.role_desc = 'PRIMARY'
END
"
## Connect to the data source and open it
$connection = New-Object System.Data.OleDb.OleDbConnection $connectionString
$command = New-Object System.Data.OleDb.OleDbCommand $sqlCommand,$connection
$connection.Open()
## Execute T-SQL command in variable, fetch the results, and close the connection
$adapter = New-Object System.Data.OleDb.OleDbDataAdapter $command
#$dataset = New-Object System.Data.DataSet
[void] $adapter.Fill($dataSet)
$connection.Close()
}
## Return all of the rows from dataset object
$dataSet.Tables | FT -AutoSize

We can examine the script parts by functionality. The first section is where we create the dataset to hold results from T-SQL queries and create the collection of SQL instances to iterate through.

## Setup dataset to hold results
$dataset = New-Object System.Data.DataSet
## populate variable with collection of SQL instances
$serverlist='DBS04A','DBS04B','DBS05A','DBS05B','DBS06A','DBS06B'

The next section is where we loop through the collection of SQL instances in the variable, first setting up the connection string then using a variable to hold the T-SQL command to be executed. Finally we connect to the SQL instance, execute the T-SQL and take any records returned and place them in the dataset object. After the loop is finished going through the collection of objects we dump the rows to the command console screen.

## Setup connection to SQL server inside loop and run T-SQL against instance 
foreach($Server in $serverlist) {
$connectionString = "Provider=sqloledb;Data Source=$Server;Initial Catalog=Master;Integrated Security=SSPI;"
## place the T-SQL in variable to be executed by OLEDB method
$sqlcommand="
IF SERVERPROPERTY ('IsHadrEnabled') = 1
BEGIN
SELECT
   AGC.name
 , RCS.replica_server_name
 , ARS.role_desc
 , AGL.dns_name
FROM
 sys.availability_groups_cluster AS AGC
  INNER JOIN sys.dm_hadr_availability_replica_cluster_states AS RCS
   ON
    RCS.group_id = AGC.group_id
  INNER JOIN sys.dm_hadr_availability_replica_states AS ARS
   ON
    ARS.replica_id = RCS.replica_id
  INNER JOIN sys.availability_group_listeners AS AGL
   ON
    AGL.group_id = ARS.group_id
WHERE
 ARS.role_desc = 'PRIMARY'
END
"
## Connect to the data source and open it
$connection = New-Object System.Data.OleDb.OleDbConnection $connectionString
$command = New-Object System.Data.OleDb.OleDbCommand $sqlCommand,$connection
$connection.Open()
## Execute T-SQL command in variable, fetch the results, and close the connection
$adapter = New-Object System.Data.OleDb.OleDbDataAdapter $command
#$dataset = New-Object System.Data.DataSet
[void] $adapter.Fill($dataSet)
$connection.Close()
}
## Return all of the rows from dataset object
$dataSet.Tables | FT -AutoSize

At this point we are done and can read the list on screen. Alternatively, we could write to a file on disk, push the data into a SQL table or even email it.

Code to email a file on disk as an attachment

If you want an example of mailing it then here is a code snippet that will mail a file on disk as an attachment.

$from = New-Object System.Net.Mail.MailAddress "[email protected]"  
$to =   New-Object System.Net.Mail.MailAddress "[email protected]"  
  
# Create Message  
$message = new-object  System.Net.Mail.MailMessage $from, $to  
$message.Subject = "Availability Group Primary List"  
$message.Body = "Availability Group Primary List for our SQL Servers"

$Attachment = New-Object Net.Mail.Attachment('c:\temp\SQLAvailGroupPrimary.txt', 'text/plain')
$message.Attachments.Add($Attachment)
  
# Set SMTP Server and create SMTP Client  
$server = "smtp.mydomain.com"  
$client = new-object system.net.mail.smtpclient $server  
  
# Send the message  
"Sending an e-mail message to {0} by using SMTP host {1} port {2}." -f $to.ToString(), $client.Host, $client.Port  
try {  
   $client.Send($message)  
   "Message to: {0}, from: {1} has beens successfully sent" -f $from, $to  
}  
catch {  
  "Exception caught in CreateTestMessage: {0}" -f $Error.ToString()  
}

Examples of the results from Dashboard and PowerShell script

The images below show what we can find from the Dashboard or SSMS and what is returned from the PowerShell script. Choose Dashboard on the Availability Group node rather than one level down we will get the information we want but for each cluster we would need a tab for the Availability Groups it holds.

SSMS_DashBoard_All_AGs

If we go to a single Availability Group node.

SSMS_DashBoard_singleAG

If we go directly into the Replicas node of the Availability Group tree we can also see the Primary and other Replicas but have to do this for every Availability Group.

SSMS_PrimaryReplica

Finally, we have the results of the PowerShell script. It displays all the Primary Replica information for as many Availability Groups as you have setup.

PowerShell_Primary_Replica
Next Steps

We can get more data from the T-SQL, we just need to decide what we want and how to get it.  If there is another DMV that has other information than the ones already used we can use them. We could also use a text list or SQL table to hold instances to be used in the collection to be looped through. We can design any method to feed the objects in the collection as long as it pulls in the SQL 2012 instances in the Availability Group. Finally, we can remove the WHERE clause and return all Replicas in the T-SQL code.

If you notice in the PowerShell code the variable holding the T-SQL code is inside the loop but is does not have to be inside the loop. We can place it outside the loop but leaving it inside would allow us to use variables to modify the T-SQL string.



sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip



About the author
MSSQLTips author Brian P ODwyer Brian P ODwyer is a BI Analyst at Cook Children's Health Care System who knows both Infrastructure and SQL server

This author pledges the content of this article is based on professional experience and not AI generated.

View all my tips



Comments For This Article




Friday, July 16, 2021 - 1:04:51 PM - Jeremy Kadlec Back To Top (89022)
Samitha,

Please reference this tip - https://www.mssqltips.com/sqlservertip/6914/beautify-html-tables-email-sql-database-mail/.

Thank you,
Jeremy Kadlec
Community Co-Leader

Friday, July 16, 2021 - 11:05:49 AM - Samitha Back To Top (89019)
instead of attachment results send in email.
please provide the script for results in email by table format or html format

Thursday, November 14, 2019 - 1:53:12 PM - William Nye Back To Top (83093)

I was trying to recall how to find whether a DB is primary and ran across this post.  I thought I had recently seen an even simpler way, so I looked more and found this:

-- Check if this is the primary node and if so, proceed
If sys.fn_hadr_is_primary_replica ('MyDB' ) <> 1 
BEGIN
RAISERROR ('MyDB is not primary on this server now right now, so quit the job with success since it will run on other server.', 18,1)
END 

Granted, this may not have been available until 2016.


Tuesday, December 11, 2018 - 10:56:21 AM - Gabi Back To Top (78450)

 Great script ! Thank you


Tuesday, November 15, 2016 - 1:55:29 AM - Nishad Back To Top (43760)

 Thanks for this useful article!

 


Tuesday, April 22, 2014 - 12:44:02 AM - Venkataraman Ramasubramanian Back To Top (30441)

Thanks. Very useful article.















get free sql tips
agree to terms