In my previous article, “Five Tips
to Get Your Organisation Releasing Software Frequently” I looked at
how teams can automate processes
to speed up release frequency. In this post, I’m looking specifically at automating deployments using
the SQL Compare command line.
SQL Compare compares SQL Server schemas and deploys
the differences. It works very effectively in scenarios where only one deployment target is required – source and target databases are specified, compared, and a
change script is automatically generated and applied.
But if multiple targets exist, and pressure
to increase
the frequency of releases builds, this solution quickly becomes unwieldy.
This is where SQL Compare’s command line comes into its own. I’ve put together a PowerShell script that loops through
the Servers table and pulls out
the server and database, these are then passed
to sqlcompare.exe
to be used as target parameters. In
the example
the source database is a scripts folder, a folder structure of scripted-out database objects used by both SQL Source Control and SQL Compare.
The script can easily be adapted
to use schema snapshots.
-- Create a DeploymentTargets database and a Servers table
CREATE DATABASE DeploymentTargets
GO
USE DeploymentTargets
GO
CREATE TABLE [dbo].[Servers](
[id] [int] IDENTITY(1,1) NOT NULL,
[serverName] [nvarchar](50) NULL,
[environment] [nvarchar](50) NULL,
[databaseName] [nvarchar](50) NULL,
CONSTRAINT [PK_Servers] PRIMARY KEY CLUSTERED ([id] ASC)
)
GO
-- Now insert your target server and database details
INSERT INTO dbo.Servers ( serverName , environment , databaseName)
VALUES ( N'myserverinstance' , N'myenvironment1' , N'mydb1')
INSERT INTO dbo.Servers ( serverName , environment , databaseName)
VALUES ( N'myserverinstance' , N'myenvironment2' , N'mydb2')
Here’s
the PowerShell script you can adapt for yourself as well.
# We're holding
the server names and database names that we want
to deploy
to in a database table.
# We need
to connect
to that server
to read these details
$serverName = ""
$databaseName = "DeploymentTargets"
$authentication = "Integrated Security=SSPI"
#$authentication = "User Id=xxx;PWD=xxx" # If you are using database authentication instead of Windows authentication.
# Path
to the scripts folder we want
to deploy
to the databases
$scriptsPath = "SimpleTalk"
# Path
to SQLCompare.exe
$SQLComparePath = "C:\Program Files (x86)\Red Gate\SQL Compare 10\sqlcompare.exe"
# Create SQL connection string, and connection
$ServerConnectionString = "Data Source=$serverName;Initial Catalog=$databaseName;$authentication"
$ServerConnection = new-object system.data.SqlClient.SqlConnection($ServerConnectionString);
# Create a Dataset
to hold
the DataTable
$dataSet = new-object "System.Data.DataSet" "ServerList"
# Create a query
$query = "SET NOCOUNT ON;"
$query += "SELECT serverName, environment, databaseName "
$query += "FROM dbo.Servers; "
# Create a DataAdapter
to populate
the DataSet with
the results
$dataAdapter = new-object "System.Data.SqlClient.SqlDataAdapter" ($query, $ServerConnection)
$dataAdapter.Fill($dataSet) | Out-Null
# Close
the connection
$ServerConnection.Close()
# Populate
the DataTable
$dataTable = new-object "System.Data.DataTable" "Servers"
$dataTable = $dataSet.Tables[0]
#For every row in
the DataTable
$dataTable | FOREACH-OBJECT {
"Server Name: $($_.serverName)"
"Database Name: $($_.databaseName)"
"Environment: $($_.environment)"
# Compare
the scripts folder
to the database and synchronize
the database
to match
# NB. Have set SQL Compare
to abort on medium level warnings.
$arguments = @("/scripts1:$($scriptsPath)", "/server2:$($_.serverName)", "/database2:$($_.databaseName)", "/AbortOnWarnings:Medium") # + @("/sync" ) # Commented out
the 'sync' parameter for safety,
write-host $arguments
& $SQLComparePath $arguments
"Exit Code: $LASTEXITCODE"
# Some interesting variations
# Check that every database matches a folder.
# For example this might be a pre-deployment step
to validate everything is at
the same baseline state.
# Or a post deployment script
to validate
the deployment worked.
# An exit code of 0 means
the databases are identical.
#
# $arguments = @("/scripts1:$($scriptsPath)", "/server2:$($_.serverName)", "/database2:$($_.databaseName)", "/Assertidentical")
# Generate a report of
the difference between
the folder and each database. Generate a SQL update script for each database.
# For example use this after
the above
to generate upgrade scripts for each database
# Examine
the warnings and
the HTML diff report
to understand
how the script will
change objects
#
#$arguments = @("/scripts1:$($scriptsPath)", "/server2:$($_.serverName)", "/database2:$($_.databaseName)", "/ScriptFile:update_$($_.environment+"_"+$_.databaseName).sql", "/report:update_$($_.environment+"_"+$_.databaseName).html" , "/reportType:Interactive", "/showWarnings", "/include:Identical")
}
It’s worth noting that
the above example generates
the deployment scripts dynamically. This approach should be problem-free for
the vast majority of changes, but it is still good practice
to review and test a pre-generated deployment script prior
to deployment. An alternative approach would be
to pre-generate a single deployment script using SQL Compare, and run this en masse
to multiple targets programmatically using sqlcmd, or using a tool like SQL Multi Script. You can use
the /ScriptFile, /report, and /showWarnings flags
to generate
change scripts, difference reports and any warnings. See
the commented out example in
the PowerShell:
#$arguments = @("/scripts1:$($scriptsPath)", "/server2:$($_.serverName)", "/database2:$($_.databaseName)", "/ScriptFile:update_$($_.environment+"_"+$_.databaseName).sql", "/report:update_$($_.environment+"_"+$_.databaseName).html" , "/reportType:Interactive", "/showWarnings", "/include:Identical")
There is a drawback of running a pre-generated deployment script; it assumes that a given database target hasn’t drifted from its expected state. Often there are (rightly or wrongly) many individuals within an organization who have permissions
to alter
the production database, and changes can therefore be made outside of
the prescribed development processes.
The consequence is that at deployment time,
the applied script has been validated against a target that no longer represents reality.
The solution here would be
to add a check for drift prior
to running
the deployment script. This is achieved by using sqlcompare.exe
to compare
the target against
the expected schema snapshot using
the /Assertidentical flag. Should this return any differences (sqlcompare.exe Exit Code 79), a drift report is outputted instead of executing
the deployment script. See
the commented out example.
# $arguments = @("/scripts1:$($scriptsPath)", "/server2:$($_.serverName)", "/database2:$($_.databaseName)", "/Assertidentical")
Any checks and processes that should be undertaken prior
to a manual deployment, should also be happen during an automated deployment. You might think about triggering backups prior
to deployment – even better, automate
the verification of
the backup too.
You can use SQL Compare’s command line interface along with PowerShell
to automate multiple actions and checks that you need in your deployment process.
Automation is a practical solution where multiple targets and a higher release cadence come into play. As we know, with great power comes great responsibility – responsibility
to ensure that
the necessary checks are made so deployments remain trouble-free.
(The code sample supplied in this post automates
the simple dynamic deployment case – if you are considering more advanced automation, e.g.
the drift checks, script generation, deploying
to large numbers of targets and backup/verification, please email me at
[email protected] for further script samples or if you have further questions)