Last active
October 10, 2017 13:46
-
-
Save JCallico/7b75febc392e3b8c35f0e4147705a988 to your computer and use it in GitHub Desktop.
Use PowerShell to sync between an Azure SQL Database and a SQL Server on-premises database [Rev 1]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# prerequisites: | |
# 1. Create an Azure Database from AdventureWorksLT sample database as hub database | |
# 2. Create an Azure Database in the same region as sync database | |
# 3. Create an on premises SQL Server Database as member database | |
# 4. Update the parameters below before running the sample | |
# | |
using namespace Microsoft.Azure.Commands.Sql.DataSync.Model | |
using namespace System.Collections.Generic | |
# Hub database info | |
# Subscription id for hub database | |
$SubscriptionId = "subscription_guid" | |
# Resource group name for hub database | |
$ResourceGroupName = "ResourceGroupName" | |
# Server name for hub database | |
$ServerName = "ServerName" | |
# Database name for hub database | |
$DatabaseName = "TestHubDatabase" | |
# Sync database info | |
# Resource group name for sync database | |
$SyncDatabaseResourceGroupName = "ResourceGroupName" | |
# Server name for sync database | |
$SyncDatabaseServerName = "ServerName" | |
# Sync database name | |
$SyncDatabaseName = "SyncDatabaseName" | |
# Sync group info | |
# Sync group name | |
$SyncGroupName = "TestSyncGroup" | |
# Conflict resolution Policy. Value can be HubWin or MemberWin | |
$ConflictResolutionPolicy = "HubWin" | |
# Sync interval in seconds. Value must be no less than 300 | |
$IntervalInSeconds = 300 | |
# Member database info | |
# Member name | |
$SyncMemberName = "member" | |
# Member server name | |
$MemberServerName = "OnPremiseServer" | |
# Member database name | |
$MemberDatabaseName = "MemberDatabaseTest" | |
# Member database type. Value can be AzureSqlDatabase or SqlServerDatabase | |
$MemberDatabaseType = "SqlServerDatabase" | |
# Sync direction. Value can be Bidirectional, Onewaymembertohub, Onewayhubtomember | |
$SyncDirection = "Bidirectional" | |
#Sync Agent Info | |
$SyncAgentName = "TestSyncAgent" | |
$SyncAgentResourceGroupName = "ResourceGroupName" | |
$SyncAgentServerName = "ServerName" | |
# Other info | |
# Temp file to save the sync schema | |
$TempFile = $env:TEMP+"\syncSchema.json" | |
# List of included columns and tables in quoted name | |
$IncludedColumnsAndTables = "[SalesLT].[Address].[AddressID]", | |
"[SalesLT].[Address].[AddressLine2]", | |
"[SalesLT].[Address].[rowguid]", | |
"[SalesLT].[Address].[PostalCode]", | |
"[SalesLT].[ProductDescription]" | |
$MetadataList = [System.Collections.ArrayList]::new($IncludedColumnsAndTables) | |
add-azurermaccount | |
select-azurermsubscription -SubscriptionId $SubscriptionId | |
# Use this section if it is safe to show password in the script. | |
# Otherwise, use the PromptForCredential | |
# $User = "username" | |
# $PWord = ConvertTo-SecureString -String "password" -AsPlainText -Force | |
# $ServerCredential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $PWord | |
if (!$ServerCredential) | |
{ | |
$ServerCredential = $Host.ui.PromptForCredential("Need credential", | |
"Please enter your user name and password for server "+$ServerName+".database.windows.net", | |
"", | |
"") | |
} | |
$SyncAgent = Get-AzureRmSqlSyncAgent -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName | Where-Object {$_.SyncAgentName -eq $SyncAgentName} | |
if (!$SyncAgent) | |
{ | |
#Create a new Sync Agent | |
Write-Host "Creating new Sync Agent"$SyncAgentName | |
New-AzureRmSqlSyncAgent -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-SyncDatabaseName $SyncDatabaseName ` | |
-SyncAgentName $SyncAgentName | |
#Generate Agent Key | |
Write-Host "Generating Agent Key" | |
$AgentKey = New-AzureRmSqlSyncAgentKey -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-SyncAgentName $SyncAgentName | |
Write-Host "Use your agent key to configure the sync agent. Do this before proceeding" | |
$agentkey | |
} | |
else | |
{ | |
Write-Host "Sync Agent"$SyncAgentName" already exists" | |
} | |
#DO THE FOLLOWING BEFORE THE NEXT STEP | |
#Install the on-premises sync agent on your machine and register the sync agent using the agent key generated above to bring the sync agent online. | |
#Add the SQL server database information including server name, database name, user name, password on the configuration tool within the sync agent. | |
$SyncGroup = Get-AzureRmSqlSyncGroup -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName | Where-Object {$_.SyncGroupName -eq $SyncGroupName} | |
if (!$SyncGroup) | |
{ | |
# Create a new sync group | |
Write-Host "Creating Sync Group"$SyncGroupName | |
New-AzureRmSqlSyncGroup -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-Name $SyncGroupName ` | |
-SyncDatabaseName $SyncDatabaseName ` | |
-SyncDatabaseServerName $SyncDatabaseServerName ` | |
-SyncDatabaseResourceGroupName $SyncDatabaseResourceGroupName ` | |
-ConflictResolutionPolicy $ConflictResolutionPolicy ` | |
-DatabaseCredential $ServerCredential | |
} | |
else | |
{ | |
Write-Host "Sync Group"$SyncGroupName" already exists" | |
} | |
# Use this section if it is safe to show password in the script. | |
#$User = "username" | |
#$Password = ConvertTo-SecureString -String "password" -AsPlainText -Force | |
#$MemberCredential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $Password | |
if (!$MemberCredential) | |
{ | |
$MemberCredential = $Host.ui.PromptForCredential("Need credential", | |
"Please enter your user name and password for server "+$MemberServerName, | |
"", | |
"") | |
} | |
#Get Information from sync Agent | |
#Confirm that your SQL Server was configured | |
#Note the Database ID, you will use this as SqlServerDatabaseID for the next step. | |
$SyncAgentInfo = Get-AzureRmSqlSyncAgentLinkedDatabase ` | |
-ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-SyncAgentName $SyncAgentName | Where-Object {$_.DatabaseName -eq $MemberDatabaseName} | |
$SyncMember = Get-AzureRmSqlSyncMember -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-SyncGroupName $SyncGroupName | Where-Object {$_.SyncMemberName -eq $SyncMemberName} | |
if (!$SyncMember) | |
{ | |
# Add a new sync member | |
Write-Host "Adding member"$SyncMemberName" to the sync group" | |
New-AzureRmSqlSyncMember -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-SyncGroupName $SyncGroupName ` | |
-Name $SyncMemberName ` | |
-MemberDatabaseType $MemberDatabaseType ` | |
-SyncAgentResourceGroupName $SyncAgentResourceGroupName ` | |
-SyncAgentServerName $SyncAgentServerName ` | |
-SyncAgentName $SyncAgentName ` | |
-SyncDirection $SyncDirection ` | |
-SqlServerDatabaseID $SyncAgentInfo.DatabaseId | |
} | |
else | |
{ | |
Write-Host "Sync Member"$SyncGroupName" already exists" | |
} | |
# Refresh database schema from hub database | |
# Specify the -SyncMemberName parameter if you want to refresh schema from the member database | |
Write-Host "Refreshing database schema from hub database" | |
$StartTime= Get-Date | |
Update-AzureRmSqlSyncSchema -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-SyncGroupName $SyncGroupName | |
#Waiting for successful refresh | |
$StartTime=$StartTime.ToUniversalTime() | |
$timer=0 | |
$timeout=90 | |
# Check the log and see if refresh has gone through | |
Write-Host "Check for successful refresh" | |
$IsSucceeded = $false | |
While ($IsSucceeded -ne $true) | |
{ | |
Start-Sleep -s 10 | |
$timer=$timer+1 | |
$Details = Get-AzureRmSqlSyncSchema -SyncGroupName $SyncGroupName -ServerName $ServerName -DatabaseName $DatabaseName -ResourceGroupName $ResourceGroupName | |
if ($Details.LastUpdateTime -gt $StartTime) | |
{ | |
Write-Host "Refresh was successful" | |
$IsSucceeded = $true | |
} | |
if ($timer -eq $timeout) | |
{ | |
Write-Host "Refresh timed out" | |
break; | |
} | |
} | |
# Get the database schema | |
Write-Host "Adding tables and columns to the sync schema" | |
$databaseSchema = Get-AzureRmSqlSyncSchema -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-SyncGroupName $SyncGroupName ` | |
$databaseSchema | ConvertTo-Json -depth 5 -Compress | Out-File $TempFile | |
$newSchema = [AzureSqlSyncGroupSchemaModel]::new() | |
$newSchema.Tables = [List[AzureSqlSyncGroupSchemaTableModel]]::new(); | |
# Add columns and tables to the sync schema | |
foreach ($tableSchema in $databaseSchema.Tables) | |
{ | |
$newTableSchema = [AzureSqlSyncGroupSchemaTableModel]::new() | |
$newTableSchema.QuotedName = $tableSchema.QuotedName | |
$newTableSchema.Columns = [List[AzureSqlSyncGroupSchemaColumnModel]]::new(); | |
$addAllColumns = $false | |
if ($MetadataList.Contains($tableSchema.QuotedName)) | |
{ | |
if ($tableSchema.HasError) | |
{ | |
$fullTableName = $tableSchema.QuotedName | |
Write-Host "Can't add table $fullTableName to the sync schema" -foregroundcolor "Red" | |
Write-Host $tableSchema.ErrorId -foregroundcolor "Red" | |
continue; | |
} | |
else | |
{ | |
$addAllColumns = $true | |
} | |
} | |
foreach($columnSchema in $tableSchema.Columns) | |
{ | |
$fullColumnName = $tableSchema.QuotedName + "." + $columnSchema.QuotedName | |
if ($addAllColumns -or $MetadataList.Contains($fullColumnName)) | |
{ | |
if ((-not $addAllColumns) -and $tableSchema.HasError) | |
{ | |
Write-Host "Can't add column $fullColumnName to the sync schema" -foregroundcolor "Red" | |
Write-Host $tableSchema.ErrorId -foregroundcolor "Red" | |
} | |
elseif ((-not $addAllColumns) -and $columnSchema.HasError) | |
{ | |
Write-Host "Can't add column $fullColumnName to the sync schema" -foregroundcolor "Red" | |
Write-Host $columnSchema.ErrorId -foregroundcolor "Red" | |
} | |
else | |
{ | |
Write-Host "Adding"$fullColumnName" to the sync schema" | |
$newColumnSchema = [AzureSqlSyncGroupSchemaColumnModel]::new() | |
$newColumnSchema.QuotedName = $columnSchema.QuotedName | |
$newColumnSchema.DataSize = $columnSchema.DataSize | |
$newColumnSchema.DataType = $columnSchema.DataType | |
$newTableSchema.Columns.Add($newColumnSchema) | |
} | |
} | |
} | |
if ($newTableSchema.Columns.Count -gt 0) | |
{ | |
$newSchema.Tables.Add($newTableSchema) | |
} | |
} | |
# Convert sync schema to Json format | |
$schemaString = $newSchema | ConvertTo-Json -depth 5 -Compress | |
# workaround a powershell bug | |
#$schemaString = $schemaString.Replace('"Tables"', '"tables"').Replace('"Columns"', '"columns"').Replace('"QuotedName"', '"quotedName"').Replace('"MasterSyncMemberName"','"masterSyncMemberName"') | |
# Save the sync schema to a temp file | |
$schemaString | Out-File $TempFile | |
# Update sync schema | |
Write-Host "Updating the sync schema" | |
Update-AzureRmSqlSyncGroup -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-Name $SyncGroupName ` | |
-SchemaFile $TempFile | |
$SyncLogStartTime = Get-Date | |
# Trigger sync manually | |
Write-Host "Trigger sync manually" | |
Start-AzureRmSqlSyncGroupSync -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-SyncGroupName $SyncGroupName | |
# Check the sync log and wait until the first sync succeeded | |
Write-Host "Check the sync log" | |
$IsSucceeded = $false | |
For ($i = 0; ($i -lt 300) -and (-not $IsSucceeded); $i = $i + 10) | |
{ | |
Start-Sleep -s 10 | |
$SyncLogEndTime = Get-Date | |
$SyncLogList = Get-AzureRmSqlSyncGroupLog -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-SyncGroupName $SyncGroupName ` | |
-StartTime $SyncLogStartTime.ToUniversalTime() ` | |
-EndTime $SyncLogEndTime.ToUniversalTime() | |
if ($SynclogList.Length -gt 0) | |
{ | |
foreach ($SyncLog in $SyncLogList) | |
{ | |
if ($SyncLog.Details.Contains("Sync completed successfully")) | |
{ | |
Write-Host $SyncLog.TimeStamp : $SyncLog.Details | |
$IsSucceeded = $true | |
} | |
} | |
} | |
} | |
if ($IsSucceeded) | |
{ | |
# Enable scheduled sync | |
Write-Host "Enable the scheduled sync with 300 seconds interval" | |
Update-AzureRmSqlSyncGroup -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-Name $SyncGroupName ` | |
-IntervalInSeconds $IntervalInSeconds | |
} | |
else | |
{ | |
# Output all log if sync doesn't succeed in 300 seconds | |
$SyncLogEndTime = Get-Date | |
$SyncLogList = Get-AzureRmSqlSyncGroupLog -ResourceGroupName $ResourceGroupName ` | |
-ServerName $ServerName ` | |
-DatabaseName $DatabaseName ` | |
-SyncGroupName $SyncGroupName ` | |
-StartTime $SyncLogStartTime.ToUniversalTime() ` | |
-EndTime $SyncLogEndTime.ToUniversalTime() | |
if ($SynclogList.Length -gt 0) | |
{ | |
foreach ($SyncLog in $SyncLogList) | |
{ | |
Write-Host $SyncLog.TimeStamp : $SyncLog.Details | |
} | |
} | |
} | |
# Clean up deployment | |
# Remove-AzureRmResourceGroup -ResourceGroupName $resourcegroupname | |
# Remove-AzureRmResourceGroup -ResourceGroupName $SyncDatabaseResourceGroupName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment