This script adds all user databases which are not yet replicated to a specific SQL Server AlwaysOn Availability Group. After that a check procedure is started to check if replication state for all databases exists and are healthy. Alerts can be send to an e-mail address or to the Windows Eventlog (or both).
This script uses custum functions fnSendMail, fnWriteEventLog to send alert messages and fnSQLCmd for handeling SQL statements.
# Import DBA modules (functions)... Import-Module 'AdminFunctions.psm1' # Global config settings... [string]$cAOGroupName = "AO Group Name (LISTENER)" [string]$cAOListenerName = "LISTENER\INSTANCE" [string]$cDatabaseBackupLocation = "\\servershare$\_AlwaysOnTempDir" [string]$sSMTPServer1 = "smtp.example.com" [string]$sToAddress1 = "admin@example.com" # Functions en Procedures... function fnAddDatabase2AOGroup() { param ( [string]$sAOGroupName, [string]$sDatabaseName, [string]$sPrimaryServerName, [string]$sSecondaryServerName, [string]$sDatabaseBackupLocation ) # Create a timestamp... [string]$sDate = Get-Date -UFormat %Y%m%d%H%M # Concat strings to create path... [string]$sDatabaseBackupFile = $sDatabaseBackupLocation + "\" + $sDatabaseName + "_" + $sDate + ".bak" [string]$sDatabaseLogFile = $sDatabaseBackupLocation + "\" + $sDatabaseName + "_" + $sDate + ".trn" [string]$sAOPrimaryPath = "SQLSERVER:\SQL\" + $sPrimaryServerName + "\AvailabilityGroups\" + $sAOGroupName [string]$sAOSecondaryPath = "SQLSERVER:\SQL\" + $sSecondaryServerName + "\AvailabilityGroups\" + $sAOGroupName # Backup Data + Log on primary... Backup-SqlDatabase -Database $($sDatabaseName) -BackupFile $sDatabaseBackupFile -ServerInstance $($sPrimaryServerName) Backup-SqlDatabase -Database $($sDatabaseName) -BackupFile $sDatabaseLogFile -ServerInstance $($sPrimaryServerName) -BackupAction 'Log' # Restore Data + Log on secondary... Restore-SqlDatabase -Database $($sDatabaseName) -BackupFile $sDatabaseBackupFile -ServerInstance $($sSecondaryServerName) -NoRecovery Restore-SqlDatabase -Database $($sDatabaseName) -BackupFile $sDatabaseLogFile -ServerInstance $($sSecondaryServerName) -RestoreAction 'Log' -NoRecovery # Add Database to Availability Group... Add-SqlAvailabilityDatabase -Path $sAOPrimaryPath -Database $($sDatabaseName) Add-SqlAvailabilityDatabase -Path $sAOSecondaryPath -Database $($sDatabaseName) } function fnSetAllDatabases2FullRecovery() { param ( [string]$sDBServerName ) [string]$sQuery9 = "-- Declarations and temptables... ` DECLARE @iNumRecs int, @iRecNum int = 1, @vCommand nvarchar(max) ` CREATE TABLE #tCommands (ID int IDENTITY(1,1), Command nvarchar(max)) ` -- Insert Commands into temptable... ` INSERT INTO #tCommands (Command) ` SELECT N'ALTER DATABASE [' + name + '] SET RECOVERY FULL WITH NO_WAIT' ` FROM [master].[sys].[databases] ` WHERE recovery_model_desc = 'SIMPLE' AND name NOT in ('master', 'msdb', 'tempdb') ` -- Execute commands from temptable... ` SELECT @iNumRecs = COUNT(*) FROM #tCommands ` WHILE @iRecNum <= @iNumRecs ` BEGIN ` SELECT @vCommand = Command FROM #tCommands ` WHERE ID = @iRecNum ` -- Execute command and increase record number... ` -- EXECUTE(@vCommand) --Alternative, Not preferred... ` EXECUTE sys.sp_executesql @stmt = @vCommand ` SET @iRecNum += 1 ` END ` -- Cleanup... ` DROP TABLE #tCommands" fnSQLCmd -sInstance $($sDBServerName) -sQuery $sQuery9 -sDatabase "master" } ### Start main script... ### # First we set all databases to full recovery... fnSetAllDatabases2FullRecovery -sDBServerName $($cAOListenerName) # Retrieve AlwaysOn information from listener... [string]$sQuery0 = "-- Retrieve AllwaysOn information and status... ` SELECT nim.ag_name, arcs.replica_id, arcs.replica_server_name, arcs.join_state, ` arcs.join_state_desc, ars.is_local, ars.role, ars.role_desc, ` ars.operational_state, ars.operational_state_desc, ars.recovery_health, ` ars.recovery_health_desc, ars.synchronization_health, ars.synchronization_health_desc ` FROM [master].[sys].dm_hadr_availability_replica_states ars ` INNER JOIN [master].[sys].dm_hadr_availability_replica_cluster_states arcs ON ars.replica_id = arcs.replica_id ` INNER JOIN [master].[sys].dm_hadr_name_id_map nim ON arcs.group_id = nim.ag_id" if ($oResult0) { Remove-Variable -name oResult0 } # Force in object so we can always count... [Object[]]($oResult0) = fnSQLCmd -sInstance $($cAOListenerName) -sQuery $sQuery0 -sDatabase "master" # Retrieve primary and secondary nodes for group... foreach($rows0 in $oResult0) { if (($rows0["ag_name"] -eq $($cAOGroupName)) -and ($rows0["role"] -eq "1")) { [string]$cPrimaryServerName = $rows0["replica_server_name"] [string]$cPrimaryReplicaID = $rows0["replica_id"] } if (($rows0["ag_name"] -eq $($cAOGroupName)) -and ($rows0["role"] -eq "2")) { [string]$cSecondaryServerName = $rows0["replica_server_name"] } } # Retrieve database names... [string]$sQuery1 = "-- Return all unreplicated databases without ` SELECT name FROM [master].[sys].[databases] ` WHERE name NOT IN ('master','tempdb','model','msdb','SSISDB') ` AND replica_id IS NULL" if ($oResult1) { Remove-Variable -name oResult1 } [Object[]]($oResult1) = fnSQLCmd -sInstance $($cAOListenerName) -sQuery $sQuery1 -sDatabase "master" # Process the rows returned... foreach($rows1 in $oResult1) { if (($cPrimaryServerName) -and ($cPrimaryServerName)) { fnAddDatabase2AOGroup -sAOGroupName $cAOGroupName -sDatabaseName ($rows1["name"]) -sPrimaryServerName $cPrimaryServerName -sSecondaryServerName $cSecondaryServerName -sDatabaseBackupLocation $cDatabaseBackupLocation } } # Check database replication state... Start-Sleep -m 10000 [string]$sQuery2 = "-- Check database replication state... ` SELECT db.name, db.replica_id, synchronization_state, synchronization_health_desc ` FROM [master].[sys].[databases] db ` LEFT OUTER JOIN [master].[sys].[dm_hadr_database_replica_states] dbrs ON db.database_id = dbrs.database_id ` WHERE name NOT IN ('master','tempdb','model','msdb', 'SSISDB') ` AND (db.replica_id IS NULL OR synchronization_state 2)" # Force in object so we can always count... if ($oResult2) { Remove-Variable -name oResult2 } [Object[]]($oResult2) = fnSQLCmd -sInstance $($cAOListenerName) -sQuery $sQuery2 -sDatabase "master" # Send report if sync state 2 or database not replicated... if ($oResult2.Count -gt 0) { # Build HTML body... [string]$sHTML_Body1 = "th {background-color: grey;} td {background-color: lightblue;}" [string]$sHTML_Body1 += $oResult2 | ConvertTo-Html name, replica_id, synchronization_health_desc -Fragment [string]$sHTML_Body1 += "" # Build TEXT body... [string]$sResult2 = $oResult2 | Out-String [string]$sTEXT_Body1 = $sResult2 # Send messages... [string]$sSubject1 = ($cAOListenerName.Replace("\", "_")) + ":Database(s) not replicated..." fnSendMail -sSMTPSrvrName $sSMTPServer1 -sFrom (fnGetFQDN -swIsEmail) -sTo $sToAddress1 -sSubject $sSubject1 -sBody $sHTML_Body1 -swIsBodyHTML # Write to Eventlog... $sTEXT_Body1 = $sSubject1 + $sTEXT_Body1 fnWriteEventLog -sNode ($cAOListenerName.Replace("\", "_")) -sMessage $sTEXT_Body1 }