So I decided to use PowerShell (as this really is more of a script), with Client DAC only. The example here was very useful.
Using the MSBuild Extension Pack allows the creation of tasks to wrap the PowerShell boilerplate.
Note that you must install PowerShell 3 to access DAC 3.0, which is best achieved by installing the Windows Management Framework 3.0.
TaskFactory wrappers as follows:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This custom task encapsulates DAC Framework 3.0 BACPAC import and export routines,
which are compatible with SQL Server 2012 and later, and SQL Azure -->
<!-- Required Import to use MSBuild Extension Pack -->
<PropertyGroup>
<AssemblyFile>$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.TaskFactory.PowerShell.dll</AssemblyFile>
<SqlServerDacDll>C:\Program Files (x86)\Microsoft SQL Server\110\DAC\bin\Microsoft.SqlServer.Dac.dll</SqlServerDacDll>
</PropertyGroup>
<UsingTask TaskFactory="PowershellTaskFactory" TaskName="ExportBacpac" AssemblyFile="$(AssemblyFile)">
<ParameterGroup>
<ConnectionString Required="true" ParameterType="System.String" />
<BacpacFile Required="true" ParameterType="System.String" />
<DatabaseName Required="true" ParameterType="System.String" />
</ParameterGroup>
<Task>
<![CDATA[
$log.LogMessage([Microsoft.Build.Framework.MessageImportance]"High","Starting export of database '$databasename' to '$bacpacfile' with connection string '$connectionstring' ")
add-type -path "$(SqlServerDacDll)"
$d = new-object Microsoft.SqlServer.Dac.DacServices $connectionstring
register-objectevent -in $d -eventname Message -source "msg" -action { $log.LogMessage([Microsoft.Build.Framework.MessageImportance]"High", $Event.SourceArgs[1].Message.Message) }
$d.exportbacpac($bacpacfile, $databasename)
unregister-event -source "msg"
$log.LogMessage([Microsoft.Build.Framework.MessageImportance]"High","Completed export of database '$databasename' to '$bacpacfile'")
]]>
</Task>
</UsingTask>
<UsingTask TaskFactory="PowershellTaskFactory" TaskName="ImportBacpac" AssemblyFile="$(AssemblyFile)">
<ParameterGroup>
<ConnectionString Required="true" ParameterType="System.String" />
<BacpacFile Required="true" ParameterType="System.String" />
<DatabaseName Required="true" ParameterType="System.String" /><!-- Not relevant for Azure import, which uses the Bacpac file name as the database name -->
</ParameterGroup>
<Task>
<![CDATA[
$log.LogMessage([Microsoft.Build.Framework.MessageImportance]"High","Starting import of database '$databasename' from '$bacpacfile' with connection string '$connectionstring' ")
add-type -path "$(SqlServerDacDll)"
$d = new-object Microsoft.SqlServer.Dac.DacServices $connectionstring
register-objectevent -in $d -eventname Message -source "msg" -action { $log.LogMessage([Microsoft.Build.Framework.MessageImportance]"High", $Event.SourceArgs[1].Message.Message) }
$bp = [Microsoft.SqlServer.Dac.BacPackage]::Load($bacpacfile)
$d.importbacpac($bp, $databasename)
unregister-event -source "msg"
$log.LogMessage([Microsoft.Build.Framework.MessageImportance]"High","Completed import of database '$databasename' from '$bacpacfile'")
]]>
</Task>
</UsingTask>
</Project>
Sample Target to call these wrappers would look as follows:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="BacpacImportExport.xml"/>
<PropertyGroup>
<TempBacpacFile>$(ReleaseFolderPublish)\$(DestinationDBName).bacpac</TempBacpacFile>
</PropertyGroup>
<Target Name="CopyAndReplaceDatabaseViaBacpac">
<Message Text="Clean bacpac directory"/>
<Exec Command="mkdir $(ReleaseFolderPublish)\" IgnoreExitCode="true"></Exec>
<Exec Command="del /Q $(ReleaseFolderPublish)\*.bacpac " IgnoreExitCode="true"></Exec>
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ReportBuildProgress" Properties="Message=Exporting database to BACPAC from source"/>
<ExportBacpac
ConnectionString="$(SourceConnectionString)"
BacpacFile="$(TempBacpacFile)"
DatabaseName="$(SourceDBName)"
/>
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ReportBuildProgress" Properties="Message=Dropping database from destination (does not fail on error)"/>
<MSBuild.ExtensionPack.SqlServer.SqlCmd TaskAction="Execute" Server="$(DestinationDBServer)" Database="master"
LogOn="$(DestinationDBUser)" Password="$(DestinationDBPass)"
CommandLineQuery="DROP DATABASE [$(DestinationDBName)];"
RedirectStandardError="true" SeverityLevel="1" />
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ReportBuildProgress" Properties="Message=Importing database from BACPAC to destination"/>
<ImportBacpac
ConnectionString="$(DestinationConnectionString)"
BacpacFile="$(TempBacpacFile)"
DatabaseName="$(DestinationDBName)"
/>
</Target>
</Project>
This could easily be amended to use the Hosted DAC by calling the reference implementation console app via <Exec Command="" />
instead of calling <ImportBacpac />
.
If you spot any improvements let me know!