323 lines
17 KiB
C#
323 lines
17 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Xml.Serialization;
|
|
using BismNormalizer.TabularCompare;
|
|
using BismNormalizer.TabularCompare.Core;
|
|
|
|
namespace BismNormalizer.CommandLine
|
|
{
|
|
class Program
|
|
{
|
|
private const int ERROR_SUCCESS = 0;
|
|
private const int ERROR_FILE_NOT_FOUND = 2;
|
|
private const int ERROR_BAD_ARGUMENTS = 160;
|
|
private const int ERROR_GENERIC_NOT_MAPPED = 1360;
|
|
private const int ERROR_NULL_REF_POINTER = 1780;
|
|
|
|
static int Main(string[] args)
|
|
{
|
|
string bsmnFile = null;
|
|
string logFile = null;
|
|
string scriptFile = null;
|
|
List<string> skipOptions = null;
|
|
bool credsProvided = false;
|
|
string sourceUsername = "";
|
|
string sourcePassword = "";
|
|
string targetUsername = "";
|
|
string targetPassword = "";
|
|
string workspaceServer = "";
|
|
|
|
StreamWriter writer = null;
|
|
Comparison _comparison = null;
|
|
|
|
try
|
|
{
|
|
#region Argument validation / help message
|
|
|
|
if (!(args?.Length > 0))
|
|
{
|
|
Console.WriteLine("No arguments received. Exiting.");
|
|
return ERROR_BAD_ARGUMENTS;
|
|
}
|
|
else if (args[0].ToLower() == "help" || args[0].ToLower() == "?" || args[0].ToLower() == "/?" || args[0].ToLower() == "/h" || args[0].ToLower() == "/help" || args[0].ToLower() == "-help" || args[0].ToLower() == "-h")
|
|
{
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" BISM Normalizer Command-Line Utility");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" Executes BISM Normalizer in command-line mode, based on content of BSMN file");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" USAGE:");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" BismNormalizer.exe BsmnFile [/Log:LogFile] [/Script:ScriptFile] [/Skip:{MissingInSource | MissingInTarget | DifferentDefinitions}] [/CredsProvided:True|False] [/SourceUsername:SourceUsername] [/SourcePassword:SourcePassword] [/TargetUsername:TargetUsername] [/TargetPassword:TargetPassword]");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" BsmnFile : Full path to the .bsmn file.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /Log:LogFile : All messages are output to the LogFile.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /Script:ScriptFile : Does not perform actual update to target database; instead, a deployment script is generated and stored to the ScriptFile.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /Skip:{MissingInSource | MissingInTarget | DifferentDefinitions} : Skip all objects that are missing in source/missing in target/with different definitions. Can pass a comma separated list of multiple skip options; e.g. 'MissingInSource,MissingInTarget,DifferentDefinitions'.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /CredsProvided:True|False : User credentials from the command line to connect to Analysis Services.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /SourceUsername:SourceUsername : Source database username.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /SourcePassword:SourcePassword : Source database password.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /TargetUsername:TargetUsername : Target database username.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /TargetPassword:TargetPassword : Target database password.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine(" /WorkspaceServer:WorkspaceServer : For SMPROJ sources/targets only, use this workspace server instead of integrated workspace for example.");
|
|
Console.WriteLine("");
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
bsmnFile = args[0];
|
|
|
|
const string logPrefix = "/log:";
|
|
const string scriptPrefix = "/script:";
|
|
const string skipPrefix = "/skip:";
|
|
const string credsProvidedPrefix = "/credsprovided:";
|
|
const string sourceUsernamePrefix = "/sourceusername:";
|
|
const string sourcePasswordPrefix = "/sourcepassword:";
|
|
const string targetUsernamePrefix = "/targetusername:";
|
|
const string targetPasswordPrefix = "/targetpassword:";
|
|
const string workspaceServerPrefix = "/workspaceserver:";
|
|
|
|
for (int i = 1; i < args.Length; i++)
|
|
{
|
|
if (args[i].Length >= logPrefix.Length && args[i].Substring(0, logPrefix.Length).ToLower() == logPrefix)
|
|
{
|
|
logFile = args[i].Substring(logPrefix.Length, args[i].Length - logPrefix.Length).Replace("\"", "");
|
|
}
|
|
else if (args[i].Length >= scriptPrefix.Length && args[i].Substring(0, scriptPrefix.Length).ToLower() == scriptPrefix)
|
|
{
|
|
scriptFile = args[i].Substring(scriptPrefix.Length, args[i].Length - scriptPrefix.Length).Replace("\"", "");
|
|
}
|
|
else if (args[i].Length >= skipPrefix.Length && args[i].Substring(0, skipPrefix.Length).ToLower() == skipPrefix)
|
|
{
|
|
skipOptions = new List<string>(args[i].Substring(skipPrefix.Length, args[i].Length - skipPrefix.Length).Split(','));
|
|
foreach (string skipOption in skipOptions)
|
|
{
|
|
if (!(skipOption == ComparisonObjectStatus.MissingInSource.ToString() || skipOption == ComparisonObjectStatus.MissingInTarget.ToString() || skipOption == ComparisonObjectStatus.DifferentDefinitions.ToString()))
|
|
{
|
|
Console.WriteLine($"Argument '{args[i]}' is invalid. Valid skip options are '{ComparisonObjectStatus.MissingInSource.ToString()}', '{ComparisonObjectStatus.MissingInTarget.ToString()}' or '{ComparisonObjectStatus.DifferentDefinitions.ToString()}'");
|
|
return ERROR_BAD_ARGUMENTS;
|
|
}
|
|
}
|
|
}
|
|
else if (args[i].Length >= credsProvidedPrefix.Length && args[i].Substring(0, credsProvidedPrefix.Length).ToLower() == credsProvidedPrefix)
|
|
{
|
|
string credsProvidedString = args[i].Substring(credsProvidedPrefix.Length, args[i].Length - credsProvidedPrefix.Length);
|
|
if (credsProvidedString == "True")
|
|
{
|
|
credsProvided = true;
|
|
}
|
|
else if (credsProvidedString == "False")
|
|
{
|
|
credsProvided = false;
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"'{args[i]}' is not a valid argument.");
|
|
return ERROR_BAD_ARGUMENTS;
|
|
}
|
|
}
|
|
else if (args[i].Length >= sourceUsernamePrefix.Length && args[i].Substring(0, sourceUsernamePrefix.Length).ToLower() == sourceUsernamePrefix)
|
|
{
|
|
sourceUsername = args[i].Substring(sourceUsernamePrefix.Length, args[i].Length - sourceUsernamePrefix.Length);
|
|
}
|
|
else if (args[i].Length >= sourcePasswordPrefix.Length && args[i].Substring(0, sourcePasswordPrefix.Length).ToLower() == sourcePasswordPrefix)
|
|
{
|
|
sourcePassword = args[i].Substring(sourcePasswordPrefix.Length, args[i].Length - sourcePasswordPrefix.Length);
|
|
}
|
|
else if (args[i].Length >= targetUsernamePrefix.Length && args[i].Substring(0, targetUsernamePrefix.Length).ToLower() == targetUsernamePrefix)
|
|
{
|
|
targetUsername = args[i].Substring(targetUsernamePrefix.Length, args[i].Length - targetUsernamePrefix.Length);
|
|
}
|
|
else if (args[i].Length >= targetPasswordPrefix.Length && args[i].Substring(0, targetPasswordPrefix.Length).ToLower() == targetPasswordPrefix)
|
|
{
|
|
targetPassword = args[i].Substring(targetPasswordPrefix.Length, args[i].Length - targetPasswordPrefix.Length);
|
|
}
|
|
else if (args[i].Length >= workspaceServerPrefix.Length && args[i].Substring(0, workspaceServerPrefix.Length).ToLower() == workspaceServerPrefix)
|
|
{
|
|
workspaceServer = args[i].Substring(workspaceServerPrefix.Length, args[i].Length - workspaceServerPrefix.Length);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"'{args[i]}' is not a valid argument.");
|
|
return ERROR_BAD_ARGUMENTS;
|
|
}
|
|
}
|
|
|
|
if (logFile != null)
|
|
{
|
|
// Attempt to open output file.
|
|
writer = new StreamWriter(logFile);
|
|
// Redirect output from the console to the file.
|
|
Console.SetOut(writer);
|
|
}
|
|
|
|
#endregion
|
|
|
|
if (!File.Exists(bsmnFile))
|
|
{
|
|
throw new FileNotFoundException($"File not found {bsmnFile}");
|
|
}
|
|
Console.WriteLine($"About to deserialize {bsmnFile}");
|
|
ComparisonInfo comparisonInfo = ComparisonInfo.DeserializeBsmnFile(bsmnFile);
|
|
|
|
Console.WriteLine();
|
|
if (comparisonInfo.ConnectionInfoSource.UseProject)
|
|
{
|
|
Console.WriteLine($"Source Project File: {comparisonInfo.ConnectionInfoSource.ProjectFile}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"Source Database: {comparisonInfo.ConnectionInfoSource.ServerName};{comparisonInfo.ConnectionInfoSource.DatabaseName}");
|
|
}
|
|
|
|
if (comparisonInfo.ConnectionInfoTarget.UseProject)
|
|
{
|
|
Console.WriteLine($"Target Project: {comparisonInfo.ConnectionInfoTarget.ProjectName}");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"Target Database: {comparisonInfo.ConnectionInfoTarget.ServerName};{comparisonInfo.ConnectionInfoTarget.DatabaseName}");
|
|
}
|
|
|
|
if (!String.IsNullOrEmpty(workspaceServer))
|
|
{
|
|
Console.WriteLine($"Workspace Server: {workspaceServer}");
|
|
}
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine("--Comparing ...");
|
|
if (credsProvided)
|
|
{
|
|
if (!String.IsNullOrEmpty(workspaceServer))
|
|
{
|
|
_comparison = ComparisonFactory.CreateComparison(comparisonInfo, sourceUsername, sourcePassword, targetUsername, targetPassword, workspaceServer);
|
|
}
|
|
else
|
|
{
|
|
_comparison = ComparisonFactory.CreateComparison(comparisonInfo, sourceUsername, sourcePassword, targetUsername, targetPassword);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_comparison = ComparisonFactory.CreateComparison(comparisonInfo);
|
|
}
|
|
_comparison.ValidationMessage += HandleValidationMessage;
|
|
_comparison.Connect();
|
|
_comparison.CompareTabularModels();
|
|
|
|
if (skipOptions != null)
|
|
{
|
|
foreach (string skipOption in skipOptions)
|
|
{
|
|
SetSkipOptions(skipOption, _comparison.ComparisonObjects);
|
|
}
|
|
}
|
|
Console.WriteLine("--Done");
|
|
Console.WriteLine();
|
|
|
|
Console.WriteLine("--Validating ...");
|
|
_comparison.ValidateSelection();
|
|
Console.WriteLine("--Done");
|
|
Console.WriteLine();
|
|
|
|
if (scriptFile != null)
|
|
{
|
|
Console.WriteLine("--Generating script ...");
|
|
//Generate script
|
|
File.WriteAllText(scriptFile, _comparison.ScriptDatabase());
|
|
Console.WriteLine($"Generated script '{scriptFile}'");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("--Updating ...");
|
|
//Update target database/project
|
|
_comparison.Update();
|
|
if (comparisonInfo.ConnectionInfoTarget.UseProject)
|
|
{
|
|
Console.WriteLine($"Applied changes to project {comparisonInfo.ConnectionInfoTarget.ProjectName}.");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"Deployed changes to database {comparisonInfo.ConnectionInfoTarget.DatabaseName}.");
|
|
Console.WriteLine("Passwords have not been set for impersonation accounts (setting passwords for data sources is not supported in command-line mode). Ensure the passwords are set before processing.");
|
|
if (comparisonInfo.OptionsInfo.OptionProcessingOption != ProcessingOption.DoNotProcess)
|
|
{
|
|
Console.WriteLine("No processing has been done (processing is not supported in command-line mode).");
|
|
}
|
|
}
|
|
}
|
|
Console.WriteLine("--Done");
|
|
}
|
|
catch (FileNotFoundException exc)
|
|
{
|
|
Console.WriteLine("The following exception occurred:");
|
|
Console.WriteLine(exc.ToString());
|
|
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
catch (ArgumentNullException exc)
|
|
{
|
|
Console.WriteLine("The following exception occurred. Try re-saving the BSMN file from Visual Studio using latest version of BISM Normalizer to ensure all necessary properties are deserialized and stored in the file.");
|
|
Console.WriteLine();
|
|
Console.WriteLine(exc.ToString());
|
|
|
|
return ERROR_NULL_REF_POINTER;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
Console.WriteLine("The following exception occurred:");
|
|
Console.WriteLine(exc.ToString());
|
|
|
|
return ERROR_GENERIC_NOT_MAPPED;
|
|
}
|
|
finally
|
|
{
|
|
if (writer != null)
|
|
{
|
|
writer.Close();
|
|
}
|
|
if (_comparison != null)
|
|
{
|
|
_comparison.Disconnect();
|
|
}
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
private static void SetSkipOptions(string skipOption, List<ComparisonObject> comparisonObjects)
|
|
{
|
|
foreach (ComparisonObject comparisonObj in comparisonObjects)
|
|
{
|
|
if (((skipOption == ComparisonObjectStatus.MissingInSource.ToString() && comparisonObj.Status == ComparisonObjectStatus.MissingInSource) ||
|
|
(skipOption == ComparisonObjectStatus.MissingInTarget.ToString() && comparisonObj.Status == ComparisonObjectStatus.MissingInTarget) ||
|
|
(skipOption == ComparisonObjectStatus.DifferentDefinitions.ToString() && comparisonObj.Status == ComparisonObjectStatus.DifferentDefinitions)
|
|
) && comparisonObj.MergeAction != MergeAction.Skip
|
|
)
|
|
{
|
|
comparisonObj.MergeAction = MergeAction.Skip;
|
|
string objectName = (string.IsNullOrEmpty(comparisonObj.SourceObjectName) ? comparisonObj.TargetObjectName : comparisonObj.SourceObjectName).TrimStart();
|
|
Console.WriteLine($"Skip due to /Skip argument {skipOption} on {comparisonObj.ComparisonObjectType.ToString()} object {objectName}");
|
|
}
|
|
|
|
SetSkipOptions(skipOption, comparisonObj.ChildComparisonObjects);
|
|
}
|
|
}
|
|
|
|
private static void HandleValidationMessage(object sender, ValidationMessageEventArgs e)
|
|
{
|
|
Console.WriteLine($"{e.ValidationMessageStatus.ToString()} Message for {e.ValidationMessageType.ToString()}: {e.Message}");
|
|
}
|
|
}
|
|
}
|