2017-07-01 10:03:33 +08:00
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 ;
2019-07-25 14:18:15 +08:00
bool credsProvided = false ;
string sourceUsername = "" ;
string sourcePassword = "" ;
string targetUsername = "" ;
string targetPassword = "" ;
2019-07-26 08:44:21 +08:00
string workspaceServer = "" ;
2019-07-25 14:18:15 +08:00
2017-07-01 10:03:33 +08:00
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 ( "" ) ;
2019-07-25 14:18:15 +08:00
Console . WriteLine ( " BismNormalizer.exe BsmnFile [/Log:LogFile] [/Script:ScriptFile] [/Skip:{MissingInSource | MissingInTarget | DifferentDefinitions}] [/CredsProvided:True|False] [/SourceUsername:SourceUsername] [/SourcePassword:SourcePassword] [/TargetUsername:TargetUsername] [/TargetPassword:TargetPassword]" ) ;
2017-07-01 10:03:33 +08:00
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 ( "" ) ;
2019-07-25 14:18:15 +08:00
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 ( "" ) ;
2019-07-26 08:44:21 +08:00
Console . WriteLine ( " /WorkspaceServer:WorkspaceServer : For SMPROJ sources/targets only, use this workspace server instead of integrated workspace for example." ) ;
Console . WriteLine ( "" ) ;
2017-07-01 10:03:33 +08:00
return ERROR_SUCCESS ;
}
bsmnFile = args [ 0 ] ;
const string logPrefix = "/log:" ;
const string scriptPrefix = "/script:" ;
const string skipPrefix = "/skip:" ;
2019-07-25 14:18:15 +08:00
const string credsProvidedPrefix = "/credsprovided:" ;
const string sourceUsernamePrefix = "/sourceusername:" ;
const string sourcePasswordPrefix = "/sourcepassword:" ;
const string targetUsernamePrefix = "/targetusername:" ;
const string targetPasswordPrefix = "/targetpassword:" ;
2019-07-26 08:44:21 +08:00
const string workspaceServerPrefix = "/workspaceserver:" ;
2017-07-01 10:03:33 +08:00
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 ;
}
}
}
2019-07-25 14:18:15 +08:00
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 ) ;
}
2019-07-26 08:44:21 +08:00
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 ) ;
}
2017-07-01 10:03:33 +08:00
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}" ) ;
2019-09-14 12:30:22 +08:00
ComparisonInfo comparisonInfo = ComparisonInfo . DeserializeBsmnFile ( bsmnFile , "BISM Normalizer Command Line" ) ;
2017-07-01 10:03:33 +08:00
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}" ) ;
}
2019-07-26 08:44:21 +08:00
if ( ! String . IsNullOrEmpty ( workspaceServer ) )
{
Console . WriteLine ( $"Workspace Server: {workspaceServer}" ) ;
}
2017-07-01 10:03:33 +08:00
Console . WriteLine ( ) ;
Console . WriteLine ( "--Comparing ..." ) ;
2019-07-25 14:18:15 +08:00
if ( credsProvided )
{
2019-09-14 12:30:22 +08:00
comparisonInfo . CredsProvided = true ;
comparisonInfo . SourceUsername = sourceUsername ;
comparisonInfo . SourcePassword = sourcePassword ;
comparisonInfo . TargetUsername = targetUsername ;
comparisonInfo . TargetPassword = targetPassword ;
2019-07-26 08:44:21 +08:00
if ( ! String . IsNullOrEmpty ( workspaceServer ) )
{
2019-09-14 12:30:22 +08:00
comparisonInfo . WorkspaceServerProvided = true ;
comparisonInfo . WorkspaceServer = workspaceServer ;
2019-07-26 08:44:21 +08:00
}
2019-07-25 14:18:15 +08:00
}
2019-09-14 12:30:22 +08:00
_comparison = ComparisonFactory . CreateComparison ( comparisonInfo ) ;
2017-07-01 10:03:33 +08:00
_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}." ) ;
2019-07-25 14:18:15 +08:00
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." ) ;
2017-07-01 10:03:33 +08:00
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 ( ) ) ;
2019-07-26 08:44:21 +08:00
2017-07-01 10:03:33 +08:00
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 )
{
2019-07-26 08:44:21 +08:00
if ( ( ( skipOption = = ComparisonObjectStatus . MissingInSource . ToString ( ) & & comparisonObj . Status = = ComparisonObjectStatus . MissingInSource ) | |
2017-07-01 10:03:33 +08:00
( 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}" ) ;
}
}
}