using System;
using System.IO;
using System.Text;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using Interop.DBGDBBuilder;
using Interop.DBGCompAndSync;
namespace NAntContrib.Tasks
{
///
/// DBGhost provides database build and syncronization support for SQL Server 2000.
///
[TaskName( "dbghost" )]
public class DBGhost : NAnt.Core.Task
{
#region Private Members
private string _buildSQLFile = string.Empty;
private string _settingsFile = string.Empty;
private string _reportFile = string.Empty;
private FileSet _adHocScripts;
private FileSet _logins;
private FileSet _rules;
private FileSet _staticdata;
private FileSet _procs;
private FileSet _tables;
private FileSet _triggers;
private FileSet _uddts;
private FileSet _udfs;
private FileSet _usersRoles;
private FileSet _views;
private string _sourceDatabase = string.Empty;
private string _targetServer = string.Empty;
private string _targetDatabase = string.Empty;
private string _targetUsername = string.Empty;
private string _targetPassword = string.Empty;
private bool _targetIntegratedAuth = false;
private bool _dropBuildDatabaseOnError = false;
private bool _dropBuildDatabaseOnSuccess = false;
private FileSet _defaults;
#endregion
#region Constructors
public DBGhost()
{
this._adHocScripts = new FileSet();
}
#endregion
#region Public Properties
///
/// The DBGhost settings file from which to load settings.
///
///
/// When the settings file is specified, explicit settings for this task are not used.
///
[NAnt.Core.Attributes.TaskAttribute( "settings" )]
public string SettingsFile
{
get { return _settingsFile; }
set
{
this.Log( Level.Info, "Settings File: {0}", value );
this._settingsFile = value;
}
}
///
/// The report file to be created by the
///
[NAnt.Core.Attributes.TaskAttribute( "report" )]
public string ReportFile
{
get { return _reportFile; }
set { this._reportFile = value; }
}
///
/// Gets or sets the output path for saving the SQL output used to update the target database.
///
/// The build SQL file.
[NAnt.Core.Attributes.TaskAttribute( "buildsqlfile" )]
public string BuildSQLFile
{
get { return this._buildSQLFile; }
set { this._buildSQLFile = value; }
}
///
/// Gets or sets the source database which will be used to execute the scripts before syncronizing the
///
/// The source database.
/// The source database must exist on the
[NAnt.Core.Attributes.TaskAttribute( "sourcedb" )]
public string SourceDb
{
get { return this._sourceDatabase; }
set { this._sourceDatabase = value; }
}
///
/// Gets or sets the target database to be syncronized.
///
/// The target database.
[NAnt.Core.Attributes.TaskAttribute( "targetdb" )]
public string TargetDb
{
get { return this._targetDatabase; }
set { this._targetDatabase = value; }
}
///
/// Gets or sets the target server on which the resides.
///
/// The target server.
[NAnt.Core.Attributes.TaskAttribute( "targetserver" )]
public string TargetServer
{
get { return this._targetServer; }
set { this._targetServer = value; }
}
///
/// Gets or sets the SQL username used to connect to the target server.
///
/// The target username.
/// Use in conjunction with the setting.
[NAnt.Core.Attributes.TaskAttribute( "targetusername" )]
public string TargetUsername
{
get { return this._targetUsername; }
set { this._targetUsername = value; }
}
///
/// Gets or sets the SQL account password used to connect to the target server.
///
/// The target password.
/// Use in conjunction with the setting.
[NAnt.Core.Attributes.TaskAttribute( "targetpassword" )]
public string TargetPassword
{
get { return this._targetPassword; }
set { this._targetPassword = value; }
}
///
/// Gets or sets a value indicating whether to use integrated authentication when connecting to the target SQL Server instance.
///
///
/// true use integrated authentication; otherwise, false.
///
[NAnt.Core.Attributes.TaskAttribute( "useintegratedauth" )]
public bool TargetIntegratedAuth
{
get { return this._targetIntegratedAuth; }
set { this._targetIntegratedAuth = value; }
}
///
/// Gets or sets a value indicating whether to drop the build database even if an error occurs executing the scripts.
///
///
/// true will drop the build database after an error; otherwise, false.
///
[TaskAttribute( "dropbuilddatabaseonerror" )]
public bool DropBuildDatabaseOnError
{
get { return this._dropBuildDatabaseOnError; }
set { this._dropBuildDatabaseOnError = value; }
}
///
/// Gets or sets a value indicating whether to drop the build database after a successful completion.
///
///
/// true will drop the build database on successful completion; otherwise, false.
///
[TaskAttribute( "dropbuilddatabaseonsuccess" )]
public bool DropBuildDatabaseOnSuccess
{
get { return this._dropBuildDatabaseOnSuccess; }
set { this._dropBuildDatabaseOnSuccess = value; }
}
///
/// Specifies the AdHoc scripts directories.
///
/// The AdHoc scripts.
[BuildElement( "AdHocScripts" )]
public FileSet AdHocScripts
{
get { return this._adHocScripts; }
set { this._adHocScripts = value; }
}
///
/// Specifies the default scripts directories.
///
/// The default scripts.
[BuildElement( "DefaultScripts" )]
public FileSet DefaultScripts
{
get { return this._defaults; }
set { this._defaults = value; }
}
///
/// Specifies the Login scripts directories.
///
/// The Login scripts.
[BuildElement( "LoginScripts" )]
public FileSet LoginScripts
{
get { return this._logins; }
set { this._logins = value; }
}
///
/// Specifies the rule scripts directories.
///
/// The rule scripts.
[BuildElement( "RuleScripts" )]
public FileSet RuleScripts
{
get { return this._rules; }
set { this._rules = value; }
}
///
/// Specifies the static scripts directories.
///
/// The static data scripts.
[BuildElement( "StaticDataScripts" )]
public FileSet StaticDataScripts
{
get { return this._staticdata; }
set { this._staticdata = value; }
}
///
/// Specifies the procedure scripts directories.
///
/// The procedure scripts.
[BuildElement( "ProcedureScripts" )]
public FileSet ProcedureScripts
{
get { return this._procs; }
set { this._procs = value; }
}
///
/// Specifies the table scripts directories.
///
/// The table scripts.
[BuildElement( "TableScripts" )]
public FileSet TableScripts
{
get { return this._tables; }
set { this._tables = value; }
}
///
/// Specifies the trigger scripts directories.
///
/// The trigger scripts.
[BuildElement( "TriggerScripts" )]
public FileSet TriggerScripts
{
get { return this._triggers; }
set { this._triggers = value; }
}
///
/// Specifies the user-defined-data-type scripts directories.
///
/// The user-defined-data-type scripts.
[BuildElement( "UddtScripts" )]
public FileSet UddtScripts
{
get { return this._uddts; }
set { this._uddts = value; }
}
///
/// Specifies the user-defined-function scripts directories.
///
/// The user-defined-function scripts.
[BuildElement( "UdfScripts" )]
public FileSet UdfScripts
{
get { return this._udfs; }
set { this._udfs = value; }
}
///
/// Specifies the user/role scripts directories.
///
/// The user/role scripts.
[BuildElement( "UserRoleScripts" )]
public FileSet UserRoleScripts
{
get { return _usersRoles; }
set { this._usersRoles = value; }
}
///
/// Specifies the view scripts directories.
///
/// The view role scripts.
[BuildElement( "ViewScripts" )]
public FileSet ViewScripts
{
get { return _views; }
set { this._views = value; }
}
#endregion
#region Overrides
[System.Security.Permissions.FileIOPermission( System.Security.Permissions.SecurityAction.Demand )]
protected override void ExecuteTask()
{
ExecuteTaskDelegate buildInvoke = new ExecuteTaskDelegate( ExecuteTaskInternal );
IAsyncResult result = buildInvoke.BeginInvoke( null, null );
result.AsyncWaitHandle.WaitOne();
buildInvoke.EndInvoke( result );
}
#endregion
#region Private Methods
private delegate void ExecuteTaskDelegate();
private void ExecuteTaskInternal()
{
Build builder = new BuildClass();
bool successful = false;
try
{
BuildNewDatabase( builder );
CompareAndSyncronizeDatabase( this._targetServer, builder.NameOfSourceDatabase, this._targetServer, this._targetDatabase, builder );
successful = true;
}
catch( Exception )
{
throw;
}
finally
{
builder.Disconnect();
if( (successful && this._dropBuildDatabaseOnSuccess) ||
(!successful && this._dropBuildDatabaseOnError) )
{
this.Log( Level.Debug, "Dropping build database {0}", builder.NameOfSourceDatabase );
builder.DropDatabase();
this.Log( Level.Info, "Build database {0} dropped successfully.", builder.NameOfSourceDatabase );
}
}
}
private bool BuildNewDatabase( Build builder )
{
builder.DatabaseBuildStatus += new __Build_DatabaseBuildStatusEventHandler(builder_DatabaseBuildStatus);
builder.DropDatabaseQuestion += new __Build_DropDatabaseQuestionEventHandler(builder_DropDatabaseQuestion);
ApplyBuilderSettings( builder );
ApplyBuilderSourcePaths( builder );
bool successful;
object o = new object();
BuildStatus status = builder.ProcessScripts( ref o );
successful = (bool)o;
switch( status )
{
case BuildStatus.Failed:
throw new BuildException( "Errors while processing scripts.", this.Location );
case BuildStatus.Executing:
throw new BuildException( "Something really really weird happened, cause we finished while the status says we are still running. Status = Executing", this.Location );
case BuildStatus.Attention:
this.Log( Level.Info, "New database created with warnings." );
break;
case BuildStatus.Succeeded:
this.Log( Level.Info, "New database created successfully." );
break;
default:
break;
}
return successful;
}
private void CompareAndSyncronizeDatabase( string sourceServer, string sourceDb, string targetServer, string targetDb, Build builder )
{
//Sync the target database
CompAndSync sync = new CompAndSyncClass();
sync.AppRoleRequiresPassword += new __CompAndSync_AppRoleRequiresPasswordEventHandler(sync_AppRoleRequiresPassword);
sync.ComparisonResults += new __CompAndSync_ComparisonResultsEventHandler(sync_ComparisonResults);
sync.DatabaseBuildStatus += new __CompAndSync_DatabaseBuildStatusEventHandler(sync_DatabaseBuildStatus);
sync.ServerSource = sourceServer;
sync.dbSource = sourceDb;
sync.ServerTarget = targetServer;
sync.dbTarget = targetDb;
this.Log( Level.Info, "Comparing Source: [{0}].[{1}] to Target [{2}].[{3}]", sync.ServerSource, sync.dbSource, sync.ServerTarget, sync.dbTarget );
sync.UseNTAuthenticationSource = true;
sync.UseNTAuthenticationTarget = true;
sync.CheckDefaults = (builder.FoldersForDefaults != null && builder.FoldersForDefaults.Count() > 0);
sync.CheckLogins = (builder.FoldersForLogins != null && builder.FoldersForLogins.Count() > 0);
sync.CheckRules = (builder.FoldersForRules != null && builder.FoldersForRules.Count() > 0);
sync.CheckData = (builder.FoldersForStaticData != null && builder.FoldersForStaticData.Count() > 0);
sync.CheckStoredProcs = (builder.FoldersForStoredProcedures != null && builder.FoldersForStoredProcedures.Count() > 0);
sync.CheckTables = (builder.FoldersForTables != null && builder.FoldersForTables.Count() > 0);
sync.CheckTriggers = (builder.FoldersForTriggers != null && builder.FoldersForTriggers.Count() > 0);
sync.CheckUserDefinedDataTypes = (builder.FoldersForUserDefinedDataTypes != null && builder.FoldersForUserDefinedDataTypes.Count() > 0);
sync.CheckUserDefinedFunctions = (builder.FoldersForUserDefinedFunctions != null && builder.FoldersForUserDefinedFunctions.Count() > 0);
sync.CheckUsersAndRoles = (builder.FoldersForUsersAndRoles != null && builder.FoldersForUsersAndRoles.Count() > 0);
sync.CheckViews = (builder.FoldersForViews != null && builder.FoldersForViews.Count() > 0);
sync.CheckForDuplicates = true;
sync.CheckForDifferences = true;
sync.CheckForPermissions = true;
sync.CheckOrdinalPosition = true;
sync.CheckForExtra = true;
sync.CheckForMissing = true;
sync.DisableFKsOnDataActions = false;
sync.FireTriggersOnDataActions = false;
sync.ExcludeExtendedProperties = true;
sync.IgnoreCollations = true;
sync.UpdateDBAutomatically = true;
sync.DynamicallySelTablesForDataCheck = true;
sync.DropDBAfterProcessing = false;
sync.CalledByCommandLine = true;
sync.ChangeSetting = DBOperation.MakeChanges;
sync.Task = ProcessTasks.SyncTwoExistingDBs;
ProcessStatus syncStatus;
try
{
syncStatus = sync.Process();
}
finally
{
sync.StopProcess();
sync.Disconnect();
}
switch( syncStatus )
{
case ProcessStatus.Failed:
throw new BuildException( "Comparison and synchronization failed." );
case ProcessStatus.Attention:
case ProcessStatus.Succeeded:
default:
break;
}
}
private void ApplyBuilderSettings( Build builder )
{
//Load settings file option?
if( this._settingsFile != null && this._settingsFile.Length > 0 )
{
if( !File.Exists(this._settingsFile) )
{
throw new BuildException( string.Format("Settings file {0} does not exist or access denied.", this._settingsFile), this.Location );
}
else
{
this.Log( Level.Info, "Loading settings from {0}", this._settingsFile );
if( !builder.ReadInSettingsXML(this._settingsFile) )
{
this.Log( Level.Info, "Unable to load settings." );
throw new BuildException( "Unable to read in settings.", this.Location );
}
else
{
this.Log( Level.Info, "Settings loaded successfully" );
}
}
}
/* The VSS options of DBGhost are not supported in this task such that the plethora of
* source control systems supported by NAnt and other Configuration Management tools
* are not wasted. */
builder.UseVisualSourceSafe = false;
builder.GetLatestFilesFromVSS = false;
builder.VisualSourceSafeDBiniFile = string.Empty;
builder.VisualSourceSafeLabel = string.Empty;
builder.VisualSourceSafeUID = string.Empty;
builder.VisualSourceSafePWD = string.Empty;
if( this._targetServer != null && this._targetServer.Length > 0 )
{
builder.NameOfSQLServerTarget = this._targetServer;
}
if( this._targetUsername != null && this._targetUsername.Length > 0 )
{
builder.SQLServerTargetUID = this._targetUsername;
}
if( this._targetPassword != null && this._targetPassword.Length > 0 )
{
builder.SQLServerTargetPWD = this._targetPassword;
}
builder.UseNTAuthenticationOnTargetDB = this._targetIntegratedAuth;
if( this._targetDatabase != null && this._targetDatabase.Length > 0 )
{
builder.NameOfTargetDatabase = this._targetDatabase;
}
//Source Database name
if( this._sourceDatabase != null && this._sourceDatabase.Length > 0 )
{
builder.NameOfSourceDatabase = this._sourceDatabase;
builder.DynamicallyAssignNewDBName = false;
}
else
builder.DynamicallyAssignNewDBName = true;
// CaptureBuildSQL
if( this._buildSQLFile != null && this._buildSQLFile.Length > 0 )
{
builder.CaptureBuildSQL = true;
builder.CaptureBuildSQLFile = this._buildSQLFile;
}
else
builder.CaptureBuildSQL = false;
//Reporting
if( this._reportFile != null && this._reportFile.Length > 0 )
{
builder.ReportFileName = this._reportFile;
builder.CreateRpt = true;
}
else
builder.CreateRpt = false;
}
private void ApplyBuilderSourcePaths( Build builder )
{
//Default Folder Paths
if( this._defaults != null && this._defaults.DirectoryNames.Count > 0 )
{
foreach( string directory in this._defaults.DirectoryNames )
{
builder.FoldersForDefaultsAdd( directory );
}
}
//Logins Folder Paths
if( this._logins != null && this._logins.DirectoryNames.Count > 0 )
{
foreach( string directory in this._logins.DirectoryNames )
{
builder.FoldersForLoginsAdd( directory );
}
}
//Rules Folder Paths
if( this._rules != null && this._rules.DirectoryNames.Count > 0 )
{
foreach( string directory in this._rules.DirectoryNames )
{
builder.FoldersForRulesAdd( directory );
}
}
//StaticData Folder Paths
if( this._staticdata != null && this._staticdata.DirectoryNames.Count > 0 )
{
foreach( string directory in this._staticdata.DirectoryNames )
{
builder.FoldersForStaticDataAdd( directory );
}
}
//Store Procedure Folder Paths
if( this._procs != null && this._procs.DirectoryNames.Count > 0 )
{
foreach( string directory in this._procs.DirectoryNames )
{
builder.FoldersForStoredProceduresAdd( directory );
}
}
//Tables Folder Paths
if( this._tables != null && this._tables.DirectoryNames.Count > 0 )
{
foreach( string directory in this._tables.DirectoryNames )
{
builder.FoldersForTablesAdd( directory );
}
}
//Triggers Folder Paths
if( this._triggers != null && this._triggers.DirectoryNames.Count > 0 )
{
foreach( string directory in this._triggers.DirectoryNames )
{
builder.FoldersForTriggersAdd( directory );
}
}
//User-Defined Data Types Folder Paths
if( this._uddts != null && this._uddts.DirectoryNames.Count > 0 )
{
foreach( string directory in this._uddts.DirectoryNames )
{
builder.FoldersForUserDefinedDataTypesAdd( directory );
}
}
//User-Defined Function Folder Paths
if( this._udfs != null && this._udfs.DirectoryNames.Count > 0 )
{
foreach( string directory in this._udfs.DirectoryNames )
{
builder.FoldersForUserDefinedFunctionsAdd( directory );
}
}
//Users and Roles Folder Paths
if( this._usersRoles != null && this._usersRoles.DirectoryNames.Count > 0 )
{
foreach( string directory in this._usersRoles.DirectoryNames )
{
builder.FoldersForUsersAndRolesAdd( directory );
}
}
//Views Folder Paths
if( this._views != null && this._views.DirectoryNames.Count > 0 )
{
foreach( string directory in this._views.DirectoryNames )
{
builder.FoldersForViewsAdd( directory );
}
}
//AdHocScripts
if( this._adHocScripts != null && this._adHocScripts.DirectoryNames.Count > 0 )
{
foreach( string directory in this._adHocScripts.DirectoryNames )
{
builder.AdhocScriptsAdd( directory );
}
}
}
#endregion
#region Event Handlers
private void builder_DropDatabaseQuestion(ref bool ConfirmDropDatabase, string ServerName, string DatabaseName)
{
this.Log( Level.Debug, "Confirming the database drop for {0}.{1}", ServerName, DatabaseName );
ConfirmDropDatabase = true;
}
private void builder_DatabaseBuildStatus(BuildProcessType currentprocesstype, BuildStatus status, int currentcount, int totalcount, string message, VBA.Collection errorcollection)
{
StringBuilder errorMsgs = new StringBuilder();
if( errorcollection != null && errorcollection.Count() > 0 )
{
System.Collections.IEnumerator enumerator = errorcollection.GetEnumerator();
while( enumerator.MoveNext() )
{
errorMsgs.AppendFormat( "{0}. ", enumerator.Current );
}
}
switch( status )
{
case BuildStatus.Succeeded:
this.Log( Level.Info, "Completed {0}", message );
break;
case BuildStatus.Executing:
this.Log( Level.Info, message );
break;
case BuildStatus.Attention:
this.Log( Level.Warning, "{0}: {1}", message, errorMsgs );
break;
case BuildStatus.Failed:
this.Log( Level.Error, "{0}: {1}", message, errorMsgs );
break;
}
}
private void sync_AppRoleRequiresPassword(ref string sPWD, ref string RoleName)
{
throw new BuildException( "DBGhost NAnt task does not support AppRole Passwords." );
}
private void sync_ComparisonResults(VBA.Collection ObjectDifferences, OperationType CurrentProcessType, string ScreenTitle, string ScreenMessage, string ProcessName)
{
}
private void sync_DatabaseBuildStatus(OperationType currentprocesstype, ProcessStatus status, int currentcount, int totalcount, string message, VBA.Collection messagecollection, string tagcontent, string processname)
{
StringBuilder msgs = new StringBuilder();
if( messagecollection != null && messagecollection.Count() > 0 )
{
System.Collections.IEnumerator enumerator = messagecollection.GetEnumerator();
while( enumerator.MoveNext() )
{
msgs.AppendFormat( "{0}. ", enumerator.Current );
}
}
switch( status )
{
case ProcessStatus.Succeeded:
this.Log( Level.Info, "Completed {0}", message );
break;
case ProcessStatus.Executing:
this.Log( Level.Info, message );
break;
case ProcessStatus.Attention:
this.Log( Level.Warning, "{0}: {1}", message, msgs );
break;
case ProcessStatus.Failed:
this.Log( Level.Error, "{0}: {1}", message, msgs );
break;
}
}
#endregion
}
}