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 } }