Restore SQL Database to an AG with Powershell
#the following powershell script tries to automate the process of restoring SQL Database to an AG (Availability Group)
#Microsoft Dynamics Axapta connects to this Availabilty groups. the script invokes a command to stop the service remotly.
#the script checks for the latest database backup file from a shared location, compares the LastWriteTime to the local database backup file. if the shared backup file
#is newer it copies it to the local location.
#the scripts determines the primary replica and the secondary replicas. remove the database from AG from the primary server. then attempts a database restore as a job
#on all servers at the same time.
#the script then adds the database to the Availability Databases, and starts the service on the AOS for MS Dynamics
#load assemblies
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoEnum") | Out-Null
#read values from XML file AG_Config_v1.0.xml
[xml]$xmlconfigfile = get-content C:\scripts\AG_Config_v1.0.xml
$ag_server1 = $xmlconfigfile.configuration.ag_server1.name
$ag_server2 = $xmlconfigfile.configuration.ag_server2.name
$ag_server3 = $xmlconfigfile.configuration.ag_server3.name
$ag_instance1 = $xmlconfigfile.configuration.ag_server1.instname
$ag_instance2 = $xmlconfigfile.configuration.ag_server2.instname
$ag_instance3 = $xmlconfigfile.configuration.ag_server3.instname
$ag_availabilitygroup = $xmlconfigfile.configuration.ag_availabilitygroup.name
$ag_database = $xmlconfigfile.configuration.ag_database.name
$ag_data_path = $xmlconfigfile.configuration.ag_data_path.name
$ag_log_path = $xmlconfigfile.configuration.ag_log_path.name
$aos_server1 = $xmlconfigfile.configuration.aos_server1.name
$aos_service = $xmlconfigfile.configuration.aos_server1.aos_service.name
$backupfile_loc1 = $xmlconfigfile.configuration.backupfile_loc1.name
$backupfile_loc2 = $xmlconfigfile.configuration.backupfile_loc2.name
$BackupFileShare = $xmlconfigfile.configuration.backupfile_share.name
$db_data_logical = $xmlconfigfile.configuration.db_data_logical.name
$db_log_logical = $xmlconfigfile.configuration.db_log_logical.name
#determine if we have the latest database backup file on the local computer 1, if not copy from shared location
$db_backup_share = gci $BackupFileShare | sort LastWriteTime | select -last 1
$db_backup_file1 = gci $backupfile_loc1| sort LastWriteTime | select -last 1
if ($db_backup_share.LastWriteTime -gt $db_backup_file1.LastWriteTime){
Start-Job -Name cpjob1 -ScriptBlock {Copy-Item $args[0] $args[1]} -ArgumentList $BackupFileShare$db_backup_share,$backupfile_loc1
}
else {
echo "no copy"
}
#if ($db_backup_share.LastWriteTime -gt $db_backup_loc.LastWriteTime) {echo true } else {echo false}
#determine if we have the latest database backup file on the local computer 2, if not copy from shared location
$db_backup_file2 = gci $backupfile_loc2| sort LastWriteTime | select -last 1
if ($db_backup_share.LastWriteTime -gt $db_backup_file2.LastWriteTime){
Start-Job -Name cpjob2 -ScriptBlock {Copy-Item $args[0] $args[1]} -ArgumentList $BackupFileShare$db_backup_share,$backupfile_loc2
}
else {
echo "no copy"
}
#wait on copy jobs to finish.
echo "waiting for copy jobs to complete"
Wait-Job -Name cpjob1
Wait-Job -Name cpjob2
#determine the path of the database backup file. this variable will be used with the restore-sqldatabase cmdlet
#we select the latest backupfile based on the lastwritetime being greater than the current date -1
#we rerun the gci to get the last modified backup file after the copy process.
$db_backup_file1 = gci $backupfile_loc1| sort LastWriteTime | select -last 1
$BackupFilePath = "$backupfile_loc1$db_backup_file1"
#determine the owner node in the WFCS for the SQL AG resource. this will help us to determine the primary and secondary replicas in our AG
#supply the output to $owner1 variable and then get the name of the owner node, also assign the result to varriable $val1
$owner1 = Get-ClusterGroup -name $ag_availabilitygroup | Select-Object OwnerNode
$val1 = $owner1.OwnerNode.Name
#We have 3 servers in our AG, sinthis series of if statements checks the name of the owner node and defines the value of the "SQL instance name" varriable.
#also it determines the secondary 1 and secondary 2 server names and instance names
if ($val1 -like $ag_server1) {
$ServerPrimary = "$ag_server1\ag_instance1"
$Server2nd1 = "$ag_server2\ag_instance2"
$Server2nd2 = "$ag_server3\ag_instance3"
}
else {
if ($val1 -like $ag_server2) {
$ServerPrimary = "$ag_server2\ag_instance2"
$Server2nd1 = "$ag_server1\ag_instance1"
$Server2nd2 = "$ag_server3\ag_instance3"
}
else {
$ServerPrimary = "$ag_server3\ag_instance3"
$Server2nd1 = "$ag_server1\ag_instance1"
$Server2nd2 = "$ag_server2\ag_instance2"
}}
#define the path of the AG, this path will be used with cmdlet add-sqlavailabilitydatabase
$MyAgPrimaryPath = "SQLSERVER:\SQL\ServerPrimary\AvailabilityGroups\ag_availabilitygroup"
$MyAgSecondaryPath1 = "SQLSERVER:\SQL\Server2nd1\AvailabilityGroups\ag_availabilitygroup"
$MyAgSecondaryPath2 = "SQLSERVER:\SQL\Server2nd2\AvailabilityGroups\ag_availabilitygroup"
#stop MS Dynamics AOS service on remote server
Invoke-Command -ComputerName $aos_server1 -ScriptBlock { Get-Service -Name "$($args[0])" | Stop-Service } -ArgumentList $aos_service
#the first step in restoring database from backup file to an AG is to remove the database from the Avaialbility Databases
Remove-SqlAvailabilityDatabase -Path SQLSERVER:\sql\ServerPrimary\availabilitygroups\ag_availabilitygroup\availabilitydatabases\ag_database
#restore the database on the primary replica, from Full Backup File, with Recovery, with over write, and relocate the files. also we want to run the command as a job so we can restore -
#on other servers in the AG at the same time.
Start-Job -InitializationScript {
#load assemblies
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
#Need SmoExtended for backup
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoEnum") | Out-Null
} -Name JobP -ScriptBlock {
$serverConn = new-object ("Microsoft.SqlServer.Management.Smo.Server") $($args[0])
$serverConn.ConnectionContext.StatementTimeout = 0
#the below variables are necessary incase you have you data and log on a different location than the default.
$RelocateData = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile("$($args[5])", "$($arg[3])")
$RelocateLog = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile("$($args[6])", "$($arg[4])")
Restore-SqlDatabase -InputObject $serverConn -Database $($args[2]) -BackupFile "$($args[1])" -RelocateFile @($RelocateData,$RelocateLog) -ReplaceDatabase
} -ArgumentList $ServerPrimary,$BackupFilePath,$ag_database,$ag_data_path,$ag_log_path,$db_data_logical,$db_log_logical
#restore the database on the 1st Secondary replica, from Full Backup File, without Recovery, with over write, and relocate the files. also we want to run the command as a job so we can restore -
#on other servers in the AG at the same time.
Start-Job -InitializationScript {
#load assemblies
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
#Need SmoExtended for backup
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoEnum") | Out-Null
} -Name JobS1 -ScriptBlock {
$serverConn = new-object ("Microsoft.SqlServer.Management.Smo.Server") $($args[0])
$serverConn.ConnectionContext.StatementTimeout = 0
#the below variables are necessary incase you have you data and log on a different location than the default.
$RelocateData2 = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile("$($args[5])", "$($arg[3])")
$RelocateLog2 = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile("$($args[6])", "$($arg[4])")
Restore-SqlDatabase -InputObject $serverConn -Database $($args[2]) -BackupFile "$($args[1])" -NoRecovery -RelocateFile @($RelocateData2,$RelocateLog2) -ReplaceDatabase
} -ArgumentList $Server2nd1,$BackupFilePath,$ag_database,$ag_data_path,$ag_log_path,$db_data_logical,$db_log_logical
#restore the database on the 2nd Secondary replica, from Full Backup File, without Recovery, with over write, and relocate the files. also we want to run the command as a job so we can restore -
#on other servers in the AG at the same time.
Start-Job -InitializationScript {
#load assemblies
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
#Need SmoExtended for backup
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoEnum") | Out-Null
} -Name JobS2 -ScriptBlock {
$serverConn = new-object ("Microsoft.SqlServer.Management.Smo.Server") $($args[0])
$serverConn.ConnectionContext.StatementTimeout = 0
#the below variables are necessary incase you have you data and log on a different location than the default.
$RelocateData = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile("$($args[5])", "$($arg[3])")
$RelocateLog = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile("$($args[6])", "$($arg[4])")
Restore-SqlDatabase -InputObject $serverConn -Database $($args[2]) -BackupFile "$($args[1]) " -NoRecovery -RelocateFile @($RelocateData,$RelocateLog) -ReplaceDatabase
} -ArgumentList $Server2nd2,$BackupFilePath,$ag_database,$ag_data_path,$ag_log_path,$db_data_logical,$db_log_logical
#these wait job cmdlets are necessary so that the next set of cmdlets to readd the database to AG databases don't run before the previous jobs are finished.
Wait-Job -Name JobP
Wait-Job -Name JobS1
Wait-Job -Name JobS2
#add the Database to the Availabilty Databases. you run the same command on all nodes in the AG
Add-SqlAvailabilityDatabase -Path $MyAgPrimaryPath -Database "$ag_database"
Add-SqlAvailabilityDatabase -Path $MyAgSecondaryPath1 -Database "$ag_database"
Add-SqlAvailabilityDatabase -Path $MyAgSecondaryPath2 -Database "$ag_database"
#start MS Dynamics AOS service on remote server
Invoke-Command -ComputerName $aos_server1 -ScriptBlock { Get-Service -Name "$($args[0])" | Start-Service } -ArgumentList $aos_service
#below is the xml configuration file content. just copy the text below to notepad then save the file under the name AG_Config_v1.0.xml
<?xml version="1.0" encoding="US-ASCII"?>
<configuration>
<ag_server1>
<name>name of database server</name>
<instname>name of instance</instname>
</ag_server1>
<ag_server2>
<name>name of database</name>
<instname>name of instance</instname>
</ag_server2>
<backupfile_loc1>
<name>location of backup file on server1</name>
</backupfile_loc1>
<backupfile_loc2>
<name>location of backup file on server 2</name>
</backupfile_loc2>
<backupfile_share>
<name>location of the backup share</name>
</backupfile_share>
<ag_availabilitygroup>
<name>name of AG</name>
</ag_availabilitygroup>
<ag_database>
<name>name of database</name>
</ag_database>
<ag_data_path>
<name>datapath\datafilename.mdf</name>
</ag_data_path>
<ag_log_path>
<name>logpath\logfilename.LDF</name>
</ag_log_path>
<aos_server1>
<name>AX2009TESTDB</name>
<aos_service>
<name>aos50*01</name>
</aos_service>
</aos_server1>
<!--you can get the logical names by running the following query against the backup file [restore filelistonly from disk='x:\backupfile.bak']-->
<db_data_logical>
<name>logical data name from backup file</name>
</db_data_logical>
<db_log_logical>
<name>logical log name from backup file</name>
</db_log_logical>
</configuration>