// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // #nullable disable using System; using System.IO; using System.Reflection; using Microsoft.Data.SqlClient; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlTools.ServiceLayer.Scripting.Contracts; using Microsoft.SqlTools.SqlCore.Connection; using Microsoft.SqlTools.Utility; using static Microsoft.SqlServer.Management.SqlScriptPublish.SqlScriptOptions; namespace Microsoft.SqlTools.ServiceLayer.Scripting { /// /// Base class for all SMO scripting operations /// public abstract class SmoScriptingOperation : ScriptingOperation { private bool disposed = false; public SmoScriptingOperation(ScriptingParams parameters) { Validate.IsNotNull("parameters", parameters); this.Parameters = parameters; } protected ScriptingParams Parameters { get; set; } public string ScriptText { get; protected set; } /// /// An event can be completed by the following conditions: success, cancel, error. /// public event EventHandler CompleteNotification; /// /// Event raised when a scripting operation has made forward progress. /// public event EventHandler ProgressNotification; /// /// Event raised when a scripting operation has resolved which database objects will be scripted. /// public event EventHandler PlanNotification; protected virtual void SendCompletionNotificationEvent(ScriptingCompleteParams parameters) { this.SetCommonEventProperties(parameters); this.CompleteNotification?.Invoke(this, parameters); } protected virtual void SendProgressNotificationEvent(ScriptingProgressNotificationParams parameters) { this.SetCommonEventProperties(parameters); this.ProgressNotification?.Invoke(this, parameters); } protected virtual void SendPlanNotificationEvent(ScriptingPlanNotificationParams parameters) { this.SetCommonEventProperties(parameters); this.PlanNotification?.Invoke(this, parameters); } protected virtual void SetCommonEventProperties(ScriptingEventParams parameters) { parameters.OperationId = this.OperationId; } protected string GetServerNameFromLiveInstance(string connectionString, string azureAccessToken) { string serverName = null; using (SqlConnection connection = new SqlConnection(connectionString)) { connection.RetryLogicProvider = Connection.ReliableConnection.SqlRetryProviders.ServerlessDBRetryProvider(); if (azureAccessToken != null) { connection.AccessToken = azureAccessToken; } connection.Open(); try { ServerConnection serverConnection; if (azureAccessToken == null) { serverConnection = new ServerConnection(connection); } else { serverConnection = new ServerConnection(connection, new AzureAccessToken(azureAccessToken)); } serverName = serverConnection.TrueName; } catch (SqlException e) { Logger.Verbose( string.Format("Exception getting server name", e)); } } Logger.Verbose(string.Format("Resolved server name '{0}'", serverName)); return serverName; } protected void ValidateScriptDatabaseParams() { try { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(this.Parameters.ConnectionString); } catch (Exception e) { throw new ArgumentException(SR.ScriptingParams_ConnectionString_Property_Invalid, e); } if (this.Parameters.FilePath == null && this.Parameters.ScriptDestination != "ToEditor") { throw new ArgumentException(SR.ScriptingParams_FilePath_Property_Invalid); } else if (this.Parameters.FilePath != null && this.Parameters.ScriptDestination != "ToEditor") { if (!Directory.Exists(Path.GetDirectoryName(this.Parameters.FilePath))) { throw new ArgumentException(SR.ScriptingParams_FilePath_Property_Invalid); } } } protected static void PopulateAdvancedScriptOptions(ScriptOptions scriptOptionsParameters, object advancedOptions) { if (scriptOptionsParameters == null) { Logger.Verbose("No advanced options set, the ScriptOptions object is null."); return; } foreach (PropertyInfo optionPropInfo in scriptOptionsParameters.GetType().GetProperties()) { PropertyInfo advancedOptionPropInfo = advancedOptions.GetType().GetProperty(optionPropInfo.Name); if (advancedOptionPropInfo == null) { Logger.Warning(string.Format("Invalid property info name {0} could not be mapped to a property on SqlScriptOptions.", optionPropInfo.Name)); continue; } object optionValue = optionPropInfo.GetValue(scriptOptionsParameters, index: null); if (optionValue == null) { Logger.Verbose(string.Format("Skipping ScriptOptions.{0} since value is null", optionPropInfo.Name)); continue; } // // The ScriptOptions property types from the request will be either a string or a bool?. // The SqlScriptOptions property types from SMO will all be an Enum. Using reflection, we // map the request ScriptOptions values to the SMO SqlScriptOptions values. // try { object smoValue = null; if (optionPropInfo.PropertyType == typeof(bool?)) { if (advancedOptionPropInfo.PropertyType == typeof(bool)) { smoValue = (bool)optionValue; } else { smoValue = (bool)optionValue ? BooleanTypeOptions.True : BooleanTypeOptions.False; } } else { smoValue = Enum.Parse(advancedOptionPropInfo.PropertyType, (string)optionValue, ignoreCase: true); } Logger.Verbose(string.Format("Setting ScriptOptions.{0} to value {1}", optionPropInfo.Name, smoValue)); advancedOptionPropInfo.SetValue(advancedOptions, smoValue); } catch (Exception e) { Logger.Warning( string.Format("An exception occurred setting option {0} to value {1}: {2}", optionPropInfo.Name, optionValue, e)); } } } /// /// Disposes the scripting operation. /// public override void Dispose() { if (!disposed) { this.Cancel(); disposed = true; } } } }