// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.ServiceLayer.Connection; using System.Diagnostics; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation { public interface IRestoreDatabaseTaskDataObject { string DataFilesFolder { get; set; } string DefaultDataFileFolder { get; } bool RelocateAllFiles { get; set; } string LogFilesFolder { get; set; } string DefaultLogFileFolder { get; } List DbFiles { get; } string DefaultBackupFolder { get; } RestoreOptions RestoreOptions { get; } string DefaultStandbyFile { get; } bool IsTailLogBackupPossible { get; } bool IsTailLogBackupWithNoRecoveryPossible { get; } bool TailLogWithNoRecovery { get; set; } string TailLogBackupFile { get; set; } string DefaultTailLogbackupFile { get; } RestorePlan RestorePlan { get; } bool CloseExistingConnections { get; set; } RestoreParams RestoreParams { get; set; } bool BackupTailLog { get; set; } string DefaultSourceDbName { get; } string SourceDatabaseName { get; set; } List SourceDbNames { get; } bool OverwriteTargetDatabase { get; set; } string DefaultTargetDbName { get; } string TargetDatabaseName { get; set; } bool CanDropExistingConnections { get; } } /// /// Includes the plan with all the data required to do a restore operation on server /// public class RestoreDatabaseTaskDataObject : SmoScriptableOperationWithFullDbAccess, IRestoreDatabaseTaskDataObject { private const char BackupMediaNameSeparator = ','; private DatabaseRestorePlanner restorePlanner; private string tailLogBackupFile; private BackupSetsFilterInfo backupSetsFilterInfo = new BackupSetsFilterInfo(); private bool? isTailLogBackupPossible = null; private bool? isTailLogBackupWithNoRecoveryPossible = null; private string backupMediaList = string.Empty; private Server server; public RestoreDatabaseTaskDataObject(Server server, String databaseName) { PlanUpdateRequired = true; this.server = server; this.Util = new RestoreUtil(server); restorePlanner = new DatabaseRestorePlanner(server); if (String.IsNullOrEmpty(databaseName)) { this.restorePlanner = new DatabaseRestorePlanner(server); } else { this.restorePlanner = new DatabaseRestorePlanner(server, databaseName); this.targetDbName = databaseName; } this.restorePlanner.TailLogBackupFile = this.Util.GetDefaultTailLogbackupFile(databaseName); this.restoreOptions = new RestoreOptions(); //the server will send events in intervals of 5 percent this.restoreOptions.PercentCompleteNotification = 5; } /// /// Boolean indicating whether the relocate all files checkbox was checked /// public bool RelocateAllFiles { get; set; } /// /// Restore session id /// public string SessionId { get; set; } public string TargetDatabaseName { get { return string.IsNullOrEmpty(targetDbName) ? DefaultSourceDbName : targetDbName; } set { this.targetDbName = value; } } public bool IsValid { get { return this.Server != null && this.RestorePlanner != null && ActiveException == null; } } public RestoreParams RestoreParams { get; set; } /// /// Database names includes in the restore plan /// /// public List SourceDbNames { get { if (RestorePlanner.ReadHeaderFromMedia) { return Util.GetSourceDbNames(this.restorePlanner.BackupMediaList, this.CredentialName); } else { return Util.GetSourceDbNames(); } } } /// /// Returns the db names that have backupsets /// /// public List GetDatabaseNamesWithBackupSets() { return Util.GetSourceDbNames(); } public bool OverwriteTargetDatabase { get; set; } public string DefaultTargetDbName { get { return Server.ConnectionContext.DatabaseName; } } /// /// Current sqlserver instance /// public override Server Server { get { return server; } } /// /// Recent exception that was thrown /// Displayed at the top of the dialog /// public Exception ActiveException { get; set; } public Exception CreateOrUpdateRestorePlanException { get; set; } /// /// Add a backup file to restore plan media list /// /// public void AddFiles(string filePaths) { backupMediaList = filePaths; PlanUpdateRequired = true; if (!string.IsNullOrWhiteSpace(filePaths)) { string[] files = filePaths.Split(BackupMediaNameSeparator); files = files.Select(x => x.Trim()).ToArray(); foreach (var file in files) { if (!this.RestorePlanner.BackupMediaList.Any(x => x.Name == file)) { this.RestorePlanner.BackupMediaList.Add(new BackupDeviceItem { DeviceType = DeviceType.File, Name = file }); } } var itemsToRemove = this.RestorePlanner.BackupMediaList.Where(x => !files.Contains(x.Name)).ToList(); foreach (var item in itemsToRemove) { this.RestorePlanner.BackupMediaList.Remove(item); } } } /// /// Returns the last backup taken /// /// public string GetLastBackupTaken() { string lastBackup = string.Empty; int lastSelectedIndex = GetLastSelectedBackupSetIndex(); BackupSet lastSelectedBackupSet = lastSelectedIndex >= 0 && this.RestorePlan.RestoreOperations != null && this.RestorePlan.RestoreOperations.Count > 0 ? this.RestorePlan.RestoreOperations[lastSelectedIndex].BackupSet : null; if (this.RestorePlanner.RestoreToLastBackup && lastSelectedBackupSet != null && this.RestorePlan.RestoreOperations.Count > 0 && lastSelectedBackupSet != null) { bool isTheLastOneSelected = lastSelectedIndex == this.RestorePlan.RestoreOperations.Count - 1; DateTime backupTime = lastSelectedBackupSet.BackupStartDate; string backupTimeStr = backupTime.ToLongDateString() + " " + backupTime.ToLongTimeString(); lastBackup = isTheLastOneSelected ? string.Format(CultureInfo.CurrentCulture, SR.TheLastBackupTaken, (backupTimeStr)) : backupTimeStr; } else if (GetFirstSelectedBackupSetIndex() == 0 && !this.RestorePlanner.RestoreToLastBackup) { lastBackup = this.CurrentRestorePointInTime.Value.ToLongDateString() + " " + this.CurrentRestorePointInTime.Value.ToLongTimeString(); } return lastBackup; } public override void Execute(TaskExecutionMode mode) { RestorePlanToExecute = null; UpdateRestoreTaskObject(); if (IsValid && RestorePlan.RestoreOperations != null && RestorePlan.RestoreOperations.Any()) { GetRestorePlanForExecutionAndScript(); } base.Execute(mode); } public ConnectionInfo ConnectionInfo { get; set; } public override string ServerName { get { if (this.ConnectionInfo != null) { return this.ConnectionInfo.ConnectionDetails.ServerName; } return this.Server.Name; } } public override string DatabaseName { get { return TargetDatabaseName; } } /// /// Executes the restore operations /// public override void Execute() { try { if (RestorePlanToExecute != null) { // Restore Plan should be already created and updated at this point if (RestorePlanToExecute != null && RestorePlanToExecute.RestoreOperations.Count > 0) { RestorePlanToExecute.PercentComplete += (object sender, PercentCompleteEventArgs e) => { OnMessageAdded(new TaskMessage { Description = $"{e.Percent}%", Status = SqlTaskStatus.InProgress }); }; RestorePlanToExecute.Execute(); } } else { throw new InvalidOperationException(SR.RestoreNotSupported); } } catch(Exception ex) { Logger.Write(TraceEventType.Information, $"Failed to execute restore task. error: {ex.Message}"); throw ex; } finally { if (this.Server.ConnectionContext.IsOpen) { this.Server.ConnectionContext.Disconnect(); } } } /// /// Gets RestorePlan to perform restore and to script /// public RestorePlan GetRestorePlanForExecutionAndScript() { // One of the reasons the existing plan is not used and a new plan is created to execute is the order of the backupsets and // the last backupset is important. User can choose specific backupset in the plan to restore and // if the last backup set is not selected from the list, it means the item before that should be the real last item // with simply removing the item from the list, the order won't work correctlly. Smo considered the last item removed and // doesn't consider the previous item as the last item. Creating a new list and adding backpsets is the only way to make it work // and the reason the last backup set is important is that it has to restore with recovery mode while the others are restored with no recovery this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated. //Clear any existing exceptions as new plan is getting recreated. this.CreateOrUpdateRestorePlanException = null; bool tailLogBackup = this.RestorePlanner.BackupTailLog; if (this.PlanUpdateRequired) { this.RestorePlan = this.RestorePlanner.CreateRestorePlan(this.RestoreOptions); this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName); } RestorePlan rp = new RestorePlan(this.Server); rp.RestoreAction = RestoreActionType.Database; if (this.RestorePlan != null) { if (this.RestorePlan.TailLogBackupOperation != null && tailLogBackup) { rp.TailLogBackupOperation = this.RestorePlan.TailLogBackupOperation; } int i = 0; foreach (Restore res in this.RestorePlan.RestoreOperations) { if (this.backupSetsFilterInfo.IsBackupSetSelected(res.BackupSet)) { rp.RestoreOperations.Add(res); } i++; } } this.SetRestorePlanProperties(rp); RestorePlanToExecute = rp; return rp; } /// /// For test purpose only. The restore plan that's used to execute /// internal RestorePlan RestorePlanToExecute { get; set; } /// /// Restore Util /// public RestoreUtil Util { get; set; } /// /// SMO database restore planner used to create a restore plan /// public DatabaseRestorePlanner RestorePlanner { get { return restorePlanner; } } public bool PlanUpdateRequired { get; private set; } /// /// File to backup tail log before doing the restore /// public string TailLogBackupFile { get { return tailLogBackupFile; } set { if (tailLogBackupFile == null || !tailLogBackupFile.Equals(value)) { this.RestorePlanner.TailLogBackupFile = value; this.PlanUpdateRequired = true; this.tailLogBackupFile = value; } } } private RestoreOptions restoreOptions; public RestoreOptions RestoreOptions { get { return restoreOptions; } } private string dataFilesFolder = string.Empty; /// /// Folder for all data files when relocate all files option is used /// public string DataFilesFolder { get { if (string.IsNullOrEmpty(this.dataFilesFolder)) { this.dataFilesFolder = this.DefaultDataFileFolder; } return this.dataFilesFolder; } set { if (this.dataFilesFolder == null || !this.dataFilesFolder.Equals(value)) { try { Uri pathUri; bool fUriCreated = Uri.TryCreate(value, UriKind.Absolute, out pathUri); if (fUriCreated && pathUri.Scheme == "https") { this.dataFilesFolder = value; } else { this.dataFilesFolder = GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext)); } if (string.IsNullOrEmpty(this.dataFilesFolder)) { this.dataFilesFolder = this.Server.DefaultFile; } } catch (Exception ex) { this.ActiveException = ex; } this.UpdateDbFiles(); } } } private string logFilesFolder = string.Empty; /// /// Folder for all log files when relocate all files option is used /// public string LogFilesFolder { get { if (string.IsNullOrEmpty(this.logFilesFolder)) { this.logFilesFolder = this.DefaultLogFileFolder; } return this.logFilesFolder; } set { if (this.logFilesFolder == null || !this.logFilesFolder.Equals(value)) { try { Uri pathUri; bool fUriCreated = Uri.TryCreate(value, UriKind.Absolute, out pathUri); if (fUriCreated && pathUri.Scheme == "https") { this.logFilesFolder = value; } else { this.logFilesFolder = GetDirectoryName(value + PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext)); } if (string.IsNullOrEmpty(this.logFilesFolder)) { this.logFilesFolder = Server.DefaultLog; } } catch (Exception ex) { this.ActiveException = ex; } this.UpdateDbFiles(); } } } /// /// Determines whether [is tail log backup possible]. /// /// /// true if [is tail log backup possible]; otherwise, false. /// public bool IsTailLogBackupPossible { get { if (!isTailLogBackupPossible.HasValue) { //TODO: get the value from DatabaseRestorePlanner.IsTailLogBackupPossible if (this.Server.Version.Major < 9 || String.IsNullOrEmpty(this.restorePlanner.DatabaseName)) { isTailLogBackupPossible = false; } else { Database db = this.Server.Databases[this.RestorePlanner.DatabaseName]; if (db == null) { isTailLogBackupPossible = false; } else { db.Refresh(); if (db.Status != DatabaseStatus.Normal && db.Status != DatabaseStatus.Suspect && db.Status != DatabaseStatus.EmergencyMode) { isTailLogBackupPossible = false; } else if (db.RecoveryModel == RecoveryModel.Full || db.RecoveryModel == RecoveryModel.BulkLogged) { isTailLogBackupPossible = true; } else { isTailLogBackupPossible = false; } } } } return isTailLogBackupPossible.Value; } } /// /// Determines whether [is tail log backup with NORECOVERY possible]. /// /// /// true if [is tail log backup with NORECOVERY possible]; otherwise, false. /// public bool IsTailLogBackupWithNoRecoveryPossible { get { if (!isTailLogBackupWithNoRecoveryPossible.HasValue) { //TODO: get the value from DatabaseRestorePlanner.IsTailLogBackupWithNoRecoveryPossible string databaseName = this.RestorePlanner.DatabaseName; if (!IsTailLogBackupPossible) { isTailLogBackupWithNoRecoveryPossible = false; } else { Database db = this.Server.Databases[databaseName]; if (db == null) { isTailLogBackupWithNoRecoveryPossible = false; } else if (Server.Version.Major > 10 && db.DatabaseEngineType == DatabaseEngineType.Standalone && !String.IsNullOrEmpty(db.AvailabilityGroupName)) { isTailLogBackupWithNoRecoveryPossible = false; } else if (db.DatabaseEngineType == DatabaseEngineType.Standalone && db.IsMirroringEnabled) { isTailLogBackupWithNoRecoveryPossible = false; } else { isTailLogBackupWithNoRecoveryPossible = true; } } } return isTailLogBackupWithNoRecoveryPossible.Value; } } public string DefaultStandbyFile { get { return Util.GetDefaultStandbyFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty); } } public string DefaultTailLogbackupFile { get { return Util.GetDefaultTailLogbackupFile(this.RestorePlan != null ? this.RestorePlan.DatabaseName : string.Empty); } } /// /// Gets or sets a value indicating whether [prompt before each backup]. /// /// /// true if [prompt before each backup]; otherwise, false. /// public bool PromptBeforeEachBackup { get; set; } private void UpdateDbFiles() { try { foreach (DbFile dbFile in this.DbFiles) { string fileName = this.GetTargetDbFilePhysicalName(dbFile.PhysicalName); if (!dbFile.DbFileType.Equals("Log")) { if (!string.IsNullOrEmpty(this.DataFilesFolder)) { dbFile.PhysicalNameRelocate = CombineToServerConnectionPath(this.DataFilesFolder, fileName); } else { dbFile.PhysicalNameRelocate = fileName; } } else { if (!string.IsNullOrEmpty(this.LogFilesFolder)) { dbFile.PhysicalNameRelocate = CombineToServerConnectionPath(this.LogFilesFolder, fileName); } else { dbFile.PhysicalNameRelocate = fileName; } } } } catch (Exception ex) { this.ActiveException = ex; } } /// /// Combining the root and file name using the server connection file path seperator /// private string CombineToServerConnectionPath(string root, string filePath) { string pathSeparatorFromServerConnection = PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext); string combinedPath = PathWrapper.Combine(root, filePath); // Make sure all path seperators are server connection separator string result = combinedPath.Replace(Path.DirectorySeparatorChar.ToString(), pathSeparatorFromServerConnection); // Make sure there's not any double file seperator in the path result = result.Replace(pathSeparatorFromServerConnection + pathSeparatorFromServerConnection, pathSeparatorFromServerConnection); return result; } private string GetDirectoryName(string filePath) { string localPath = ConvertToLocalMachinePath(filePath); localPath = PathWrapper.GetDirectoryName(localPath); return ConvertToServerConnectionPath(localPath); } private string ConvertToLocalMachinePath(string filePath) { string pathSeparator = Path.DirectorySeparatorChar.ToString(); string localPath = filePath.Replace("/", pathSeparator); localPath = localPath.Replace("\\", pathSeparator); return localPath; } private string ConvertToServerConnectionPath(string filePath) { string pathSeparator = PathWrapper.PathSeparatorFromServerConnection(Server.ConnectionContext); string serverPath = filePath.Replace("/", pathSeparator); serverPath = serverPath.Replace("\\", pathSeparator); return serverPath; } /// /// Updates the Restore folder location of those db files whose orginal directory location /// is not present in the destination computer. /// internal void UpdateDBFilesPhysicalRelocate() { foreach (DbFile item in DbFiles) { string fileName = this.GetTargetDbFilePhysicalName(item.PhysicalName); item.PhysicalNameRelocate = PathWrapper.Combine(PathWrapper.GetDirectoryName(item.PhysicalName), fileName); Uri pathUri; bool fUriCreated = Uri.TryCreate(item.PhysicalNameRelocate, UriKind.Absolute, out pathUri); if ((!fUriCreated || pathUri.Scheme != Uri.UriSchemeHttps) && !Directory.Exists(Path.GetDirectoryName(item.PhysicalNameRelocate))) { string directoryPath = string.Empty; if (string.Compare(item.DbFileType, SR.Log, StringComparison.OrdinalIgnoreCase) == 0) { directoryPath = Util.GetDefaultLogFileFolder(); } else { directoryPath = Util.GetDefaultDataFileFolder(); } item.PhysicalNameRelocate = PathWrapper.Combine(directoryPath, fileName); } } } private List dbFiles = new List(); /// /// List of files of the source database or in the backup file /// public List DbFiles { get { return dbFiles; } } internal RestorePlan restorePlan; /// /// Restore plan to do the restore /// public RestorePlan RestorePlan { get { return this.restorePlan; } internal set { this.restorePlan = value; } } /// /// The database being restored /// public string targetDbName = string.Empty; /// /// The database from the backup file used to restore to by default /// public string DefaultSourceDbName { get { var dbNames = SourceDbNames; string dbName = dbNames.FirstOrDefault(); return dbName; } } /// /// Current value of source db name in the planner /// public string SourceDatabaseName { get { return this.RestorePlanner == null ? string.Empty : this.RestorePlanner.DatabaseName; } set { if(this.RestorePlanner != null) { string currentDatabaseName = this.RestorePlanner.DatabaseName; this.RestorePlanner.DatabaseName = value; } } } /// /// Gets or sets a value indicating whether [close existing connections]. /// /// /// true if [close existing connections]; otherwise, false. /// public bool CloseExistingConnections { get; set; } /* private BackupTimeLine.TimeLineDuration timeLineDuration = BackupTimeLine.TimeLineDuration.Day; public BackupTimeLine.TimeLineDuration TimeLineDuration { get { return this.timeLineDuration; } set { this.timeLineDuration = value; } } */ /// /// Sql server credential name used to restore from Microsoft Azure url /// internal string CredentialName = string.Empty; /// /// Azure container SAS policy /// internal string ContainerSharedAccessPolicy = string.Empty; /// /// Returns the physical name for the target Db file. /// It is the sourceDbName replaced with targetDbName in sourceFilename. /// If either sourceDbName or TargetDbName is empty, the source Db filename is returned. /// /// source DbFile physical location /// private string GetTargetDbFilePhysicalName(string sourceDbFilePhysicalLocation) { string filePath = ConvertToLocalMachinePath(sourceDbFilePhysicalLocation); string fileName = Path.GetFileName(filePath); if (!string.IsNullOrEmpty(this.SourceDatabaseName) && !string.IsNullOrEmpty(this.targetDbName)) { string sourceFilename = fileName; fileName = sourceFilename.Replace(this.SourceDatabaseName, this.targetDbName); } return fileName; } public IEnumerable GetBackupSetInfo() { List result = new List(); foreach (Restore restore in RestorePlan.RestoreOperations) { BackupSetInfo backupSetInfo = BackupSetInfo.Create(restore, Server); result.Add(backupSetInfo); } return result; } /// /// List of selected backupsets /// public DatabaseFileInfo[] GetSelectedBakupSets() { List result = new List(); IEnumerable backupSetInfos = GetBackupSetInfo(); foreach (var backupSetInfo in backupSetInfos) { var item = new DatabaseFileInfo(backupSetInfo.ConvertPropertiesToArray()); Guid backupSetGuid; if(!Guid.TryParse(item.Id, out backupSetGuid)) { backupSetGuid = Guid.Empty; } item.IsSelected = this.backupSetsFilterInfo.IsBackupSetSelected(backupSetGuid); result.Add(item); } return result.ToArray(); } /// /// Gets the files of the database /// public List GetDbFiles() { Database db = null; List ret = new List(); try { if (!this.RestorePlanner.ReadHeaderFromMedia) { db = this.Server.Databases[this.RestorePlanner.DatabaseName]; } if (restorePlan != null && restorePlan.RestoreOperations.Count > 0) { if (db != null && db.Status == DatabaseStatus.Normal) { ret = this.Util.GetDbFiles(db); } else { ret = this.Util.GetDbFiles(restorePlan.RestoreOperations[0]); } } } catch(Exception ex ) { Logger.Write(TraceEventType.Information, $"Failed to get restore db files. error: {ex.Message}"); } return ret; } public string DefaultDataFileFolder { get { return Util.GetDefaultDataFileFolder(); } } public string DefaultLogFileFolder { get { return Util.GetDefaultLogFileFolder(); } } public string DefaultBackupFolder { get { return CommonUtilities.GetDefaultBackupFolder(this.server.ConnectionContext); } } internal RestorePlan CreateRestorePlan(DatabaseRestorePlanner planner, RestoreOptions restoreOptions) { this.CreateOrUpdateRestorePlanException = null; RestorePlan ret = null; try { ret = planner.CreateRestorePlan(restoreOptions); if (ret == null || ret.RestoreOperations.Count == 0) { this.ActiveException = planner.GetBackupDeviceReadErrors(); } } catch (Exception ex) { this.ActiveException = ex; this.CreateOrUpdateRestorePlanException = this.ActiveException; } finally { } return ret; } private void ResetOptions() { isTailLogBackupPossible = null; isTailLogBackupWithNoRecoveryPossible = null; bool isTailLogBackupPossibleValue = IsTailLogBackupPossible; bool isTailLogBackupWithNoRecoveryPossibleValue = isTailLogBackupPossibleValue; } public void CreateNewRestorePlan() { this.ActiveException = null; //Clear any existing exceptions as the plan is getting recreated. //Clear any existing exceptions as new plan is getting recreated. this.CreateOrUpdateRestorePlanException = null; this.DbFiles.Clear(); this.PlanUpdateRequired = false; this.restorePlan = null; if (String.IsNullOrEmpty(this.RestorePlanner.DatabaseName)) { this.RestorePlan = new RestorePlan(this.Server); this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName); } else { this.RestorePlan = this.CreateRestorePlan(this.RestorePlanner, this.RestoreOptions); this.Util.AddCredentialNameForUrlBackupSet(this.restorePlan, this.CredentialName); if (this.ActiveException == null) { this.dbFiles = this.GetDbFiles(); UpdateDBFilesPhysicalRelocate(); } } if (this.restorePlan == null) { this.RestorePlan = new RestorePlan(this.Server); this.Util.AddCredentialNameForUrlBackupSet(this.RestorePlan, this.CredentialName); } else { ResetOptions(); } } public bool ShouldCreateNewPlan() { return RestorePlan == null || string.Compare(RestorePlanner.DatabaseName, this.RestoreParams.GetOptionValue(RestoreOptionsHelper.SourceDatabaseName), StringComparison.InvariantCultureIgnoreCase) != 0 || RestorePlanner.ReadHeaderFromMedia != this.RestoreParams.ReadHeaderFromMedia || this.RelocateAllFiles != this.RestoreParams.GetOptionValue(RestoreOptionsHelper.RelocateDbFiles) || string.Compare(TargetDatabaseName, this.RestoreParams.GetOptionValue(RestoreOptionsHelper.TargetDatabaseName), StringComparison.InvariantCultureIgnoreCase) != 0 || string.Compare(this.backupMediaList, RestoreParams.BackupFilePaths, StringComparison.InvariantCultureIgnoreCase) != 0; } /// /// Updates restore plan /// public void UpdateRestorePlan() { RestoreOptionsHelper.UpdateOptionsInPlan(this); if (RelocateAllFiles) { UpdateDbFiles(); } this.SetRestorePlanProperties(this.restorePlan); UpdateSelectedBackupSets(); } /// /// Determine if restore plan of selected database does have Url /// private bool IfRestorePlanHasUrl() { return (restorePlan.RestoreOperations.Any( res => res.BackupSet.BackupMediaSet.BackupMediaList.Any(t => t.MediaType == DeviceType.Url))); } /// /// Sets restore plan properties /// private void SetRestorePlanProperties(RestorePlan rp) { if (rp == null || rp.RestoreOperations.Count < 1) { return; } rp.SetRestoreOptions(this.RestoreOptions); rp.CloseExistingConnections = this.CloseExistingConnections; if (this.TargetDatabaseName != null && !this.TargetDatabaseName.Equals(string.Empty)) { rp.DatabaseName = TargetDatabaseName; } rp.RestoreOperations[0].RelocateFiles.Clear(); foreach (DbFile dbFile in this.DbFiles) { // For XStore path, we don't want to try the getFullPath. string newPhysicalPath; Uri pathUri; bool uriCreated = Uri.TryCreate(dbFile.PhysicalNameRelocate, UriKind.Absolute, out pathUri); if (uriCreated && pathUri.Scheme == "https") { newPhysicalPath = dbFile.PhysicalNameRelocate; } else { newPhysicalPath = Path.GetFullPath(dbFile.PhysicalNameRelocate); } if (!dbFile.PhysicalName.Equals(newPhysicalPath)) { RelocateFile relocFile = new RelocateFile(dbFile.LogicalName, dbFile.PhysicalNameRelocate); rp.RestoreOperations[0].RelocateFiles.Add(relocFile); } } } /// /// Bool indicating whether a tail log backup will be taken /// public bool BackupTailLog { get { return this.RestorePlanner.BackupTailLog; } set { if (this.RestorePlanner.BackupTailLog != value) { this.RestorePlanner.BackupTailLog = value; this.PlanUpdateRequired = true; } } } /// /// bool indicating whether the database will be left in restoring state /// public bool TailLogWithNoRecovery { get { return this.RestorePlanner.TailLogWithNoRecovery; } set { if (this.RestorePlanner.TailLogWithNoRecovery != value) { this.RestorePlanner.TailLogWithNoRecovery = value; this.PlanUpdateRequired = true; } } } public DateTime? CurrentRestorePointInTime { get { if (!IsAnyFullBackupSetSelected()) { return null; } for (int i = this.RestorePlan.RestoreOperations.Count - 1; i >= 0; i--) { BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet; if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet)) { if (backupSet == null || (backupSet.BackupSetType == BackupSetType.Log && this.RestorePlan.RestoreOperations[i].ToPointInTime != null)) { return this.RestorePlanner.RestoreToPointInTime; } return backupSet.BackupStartDate; } } return null; } } /// /// Returns the restore plan error message /// public override string ErrorMessage { get { if (ActiveException != null) { return ActiveException.Message; } return string.Empty; } } private bool IsAnyFullBackupSetSelected() { bool isSelected = false; if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected) { var fullBackupSet = this.RestorePlan.RestoreOperations.FirstOrDefault(x => x.BackupSet.BackupSetType == BackupSetType.Database); isSelected = fullBackupSet != null && this.backupSetsFilterInfo.IsBackupSetSelected(fullBackupSet.BackupSet.BackupSetGuid); } return isSelected; } private int GetLastSelectedBackupSetIndex() { if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected) { for (int i = this.RestorePlan.RestoreOperations.Count -1; i >= 0; i--) { BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet; if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet)) { return i; } } } return -1; } private int GetFirstSelectedBackupSetIndex() { if (this.RestorePlan != null && this.RestorePlan.RestoreOperations.Any() && this.backupSetsFilterInfo.AnySelected) { for (int i = 0; i < this.RestorePlan.RestoreOperations.Count - 1; i++) { BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet; if (this.backupSetsFilterInfo.IsBackupSetSelected(backupSet)) { return i; } } } return -1; } public void UpdateSelectedBackupSets() { this.backupSetsFilterInfo.Clear(); var selectedBackupSetsFromClient = this.RestoreParams.SelectedBackupSets; if (this.RestorePlan != null && this.RestorePlan.RestoreOperations != null) { for (int index = 0; index < this.RestorePlan.RestoreOperations.Count; index++) { BackupSet backupSet = this.RestorePlan.RestoreOperations[index].BackupSet; if (backupSet != null) { // If the collection client sent is null, select everything; otherwise select the items that are selected in client bool backupSetSelected = selectedBackupSetsFromClient == null || selectedBackupSetsFromClient.Any(x => BackUpSetGuidEqualsId(backupSet, x)); if (backupSetSelected) { AddBackupSetsToSelected(index, index); //the last index - this will include tail-Log restore operation if present //If the last item is selected, select all the items before that because the last item //is tail-log if (index == this.RestorePlan.RestoreOperations.Count - 1) { AddBackupSetsToSelected(0, index); } //If the second item is selected and it's a diff backup, the fist item (full backup) has to be selected if (index == 1 && backupSet.BackupSetType == BackupSetType.Differential) { AddBackupSetsToSelected(0, 0); } //If the selected item is a log backup, select all the items before that if (backupSet.BackupSetType == BackupSetType.Log) { AddBackupSetsToSelected(0, index); } } else { // If the first item is not selected which is always the full backup, other backupsets cannot be selected if (index == 0) { selectedBackupSetsFromClient = new string[] { }; } //The a log is not selected, the logs after that cannot be selected if (backupSet.BackupSetType == BackupSetType.Log) { break; } //If the second item is not selected and it's a diff backup if (index == 1 && backupSet.BackupSetType == BackupSetType.Differential) { if (this.Server.Version.Major < 9 || (this.RestorePlan.RestoreOperations.Count >= 3 && BackupSet.IsBackupSetsInSequence(this.RestorePlan.RestoreOperations[0].BackupSet, this.RestorePlan.RestoreOperations[2].BackupSet))) { // only the item at index 1 won't be selected } else { // nothing after index 1 should be selected break; } } } } } } } private bool BackUpSetGuidEqualsId(BackupSet backupSet, string id) { return backupSet != null && string.Compare(backupSet.BackupSetGuid.ToString(), id, StringComparison.OrdinalIgnoreCase) == 0; } private void AddBackupSetsToSelected(int from, int to) { if (this.RestorePlan != null && this.RestorePlan.RestoreOperations != null && from < this.RestorePlan.RestoreOperations.Count && to < this.RestorePlan.RestoreOperations.Count) { for (int i = from; i <= to; i++) { BackupSet backupSet = this.RestorePlan.RestoreOperations[i].BackupSet; this.backupSetsFilterInfo.Add(backupSet); } } } /// /// Verifies the backup files location. /// internal void CheckBackupFilesLocation() { if (this.RestorePlan != null) { foreach (Restore restore in this.RestorePlan.RestoreOperations) { if (restore.BackupSet != null) { restore.BackupSet.CheckBackupFilesExistence(); } } } } internal bool DbFilesLocationAreValid() { foreach (DbFile dbFile in this.DbFiles) { string newPhysicalPath = Path.GetFullPath(dbFile.PhysicalNameRelocate); if (string.Compare(dbFile.PhysicalName, dbFile.PhysicalNameRelocate, true) != 0) { bool isValidFolder = false; bool isValidPath = Util.IsDestinationPathValid(Path.GetDirectoryName(newPhysicalPath), ref isValidFolder); if (!(isValidFolder && isValidPath)) { return false; } } } return true; } /// /// Returns true if can close eixisting connections for give database /// public bool CanDropExistingConnections { get { if (RestorePlan != null && RestorePlanner != null) { return RestorePlan.CanDropExistingConnections(RestorePlanner.DatabaseName); } return false; } } /// /// Cancels the restore operations /// public override void Cancel() { foreach (Restore restore in RestorePlan.RestoreOperations) { restore.Abort(); } } /// /// Create a restore data object that includes the plan to do the restore operation /// /// /// internal void UpdateRestoreTaskObject() { bool shouldCreateNewPlan = ShouldCreateNewPlan(); if (!string.IsNullOrEmpty(RestoreParams.BackupFilePaths) && RestoreParams.ReadHeaderFromMedia) { AddFiles(RestoreParams.BackupFilePaths); } else { RestorePlanner.BackupMediaList.Clear(); } RestorePlanner.ReadHeaderFromMedia = RestoreParams.ReadHeaderFromMedia; RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.SourceDatabaseName, this); RestoreOptionFactory.Instance.SetAndValidate(RestoreOptionsHelper.TargetDatabaseName, this); if (shouldCreateNewPlan) { CreateNewRestorePlan(); } UpdateRestorePlan(); } } }