コード例 #1
0
        public override bool Execute(ProgramOptions programOptions)
        {
            Stopwatch stopWatch = new Stopwatch();

            stopWatch.Start();

            StepTiming stepTimingFunction = new StepTiming();

            stepTimingFunction.JobFileName = programOptions.ReportJobFilePath;
            stepTimingFunction.StepName    = programOptions.ReportJob.Status.ToString();
            stepTimingFunction.StepID      = (int)programOptions.ReportJob.Status;
            stepTimingFunction.StartTime   = DateTime.Now;
            stepTimingFunction.NumEntities = 0;

            this.DisplayJobStepStartingStatus(programOptions);

            this.FilePathMap = new FilePathMap(programOptions);

            try
            {
                #region Prepare the report package

                // Prepare package
                ExcelPackage excelReport = new ExcelPackage();
                excelReport.Workbook.Properties.Author   = String.Format("Snowflake Grant Report Version {0}", Assembly.GetEntryAssembly().GetName().Version);
                excelReport.Workbook.Properties.Title    = "Snowflake Grant Differences Report";
                excelReport.Workbook.Properties.Subject  = String.Format("{0}<->{1}", programOptions.LeftReportFolderPath, programOptions.RightReportFolderPath);
                excelReport.Workbook.Properties.Comments = String.Format("Command line {0}", programOptions);

                #endregion

                #region Parameters sheet

                // Parameters sheet
                ExcelWorksheet sheet = excelReport.Workbook.Worksheets.Add(SHEET_PARAMETERS);

                var hyperLinkStyle = sheet.Workbook.Styles.CreateNamedStyle("HyperLinkStyle");
                hyperLinkStyle.Style.Font.UnderLineType = ExcelUnderLineType.Single;
                hyperLinkStyle.Style.Font.Color.SetColor(colorBlueForHyperlinks);

                var objectToRolePermissionCellStyle = sheet.Workbook.Styles.CreateNamedStyle("ShortPermissionStyle");
                objectToRolePermissionCellStyle.Style.Font.Size = 8;

                fillReportParametersSheet(sheet, programOptions, excelReport.Workbook.Properties.Title);

                #endregion

                #region TOC sheet

                // Navigation sheet with link to other sheets
                sheet = excelReport.Workbook.Worksheets.Add(SHEET_TOC);

                #endregion

                #region Entity sheets and their associated pivots

                sheet = excelReport.Workbook.Worksheets.Add(SHEET_DIFFERENCES);
                sheet.Cells[1, 1].Value     = "Table of Contents";
                sheet.Cells[1, 2].Formula   = String.Format(@"=HYPERLINK(""#'{0}'!A1"", ""<Go>"")", SHEET_TOC);
                sheet.Cells[1, 2].StyleName = "HyperLinkStyle";
                sheet.Cells[2, 1].Value     = "See Pivot";
                sheet.Cells[2, 2].Formula   = String.Format(@"=HYPERLINK(""#'{0}'!A1"", ""<Go>"")", SHEET_DIFFERENCES_TYPE);
                sheet.Cells[2, 2].StyleName = "HyperLinkStyle";
                sheet.View.FreezePanes(LIST_SHEET_START_TABLE_AT + 1, 1);

                sheet = excelReport.Workbook.Worksheets.Add(SHEET_DIFFERENCES_TYPE);
                sheet.Cells[1, 1].Value     = "Table of Contents";
                sheet.Cells[1, 2].Formula   = String.Format(@"=HYPERLINK(""#'{0}'!A1"", ""<Go>"")", SHEET_TOC);
                sheet.Cells[1, 2].StyleName = "HyperLinkStyle";
                sheet.Cells[2, 1].Value     = "See Converted Data";
                sheet.Cells[2, 2].Formula   = String.Format(@"=HYPERLINK(""#'{0}'!A1"", ""<Go>"")", SHEET_DIFFERENCES);
                sheet.Cells[2, 2].StyleName = "HyperLinkStyle";
                sheet.View.FreezePanes(PIVOT_SHEET_START_PIVOT_AT + PIVOT_SHEET_CHART_HEIGHT + 2, 1);

                #endregion

                #region Report file variables

                ExcelRangeBase range = null;
                ExcelTable     table = null;

                #endregion

                #region Differences sheet

                sheet = excelReport.Workbook.Worksheets[SHEET_DIFFERENCES];

                logger.Info("{0} Sheet", sheet.Name);
                loggerConsole.Info("{0} Sheet", sheet.Name);

                EPPlusCSVHelper.ReadCSVFileIntoExcelRange(FilePathMap.Report_RoleGrant_Differences_FilePath(), 0, typeof(GrantDifference), sheet, LIST_SHEET_START_TABLE_AT, 1);

                logger.Info("{0} Sheet ({1} rows)", sheet.Name, sheet.Dimension.Rows);
                loggerConsole.Info("{0} Sheet ({1} rows)", sheet.Name, sheet.Dimension.Rows);

                if (sheet.Dimension.Rows > LIST_SHEET_START_TABLE_AT)
                {
                    range            = sheet.Cells[LIST_SHEET_START_TABLE_AT, 1, sheet.Dimension.Rows, sheet.Dimension.Columns];
                    table            = sheet.Tables.Add(range, TABLE_DIFFERENCES);
                    table.ShowHeader = true;
                    table.TableStyle = TableStyles.Medium2;
                    table.ShowFilter = true;
                    table.ShowTotal  = false;

                    sheet.Column(table.Columns["Privilege"].Position + 1).Width         = 20;
                    sheet.Column(table.Columns["ObjectType"].Position + 1).Width        = 20;
                    sheet.Column(table.Columns["ObjectName"].Position + 1).Width        = 30;
                    sheet.Column(table.Columns["GrantedTo"].Position + 1).Width         = 30;
                    sheet.Column(table.Columns["Difference"].Position + 1).Width        = 20;
                    sheet.Column(table.Columns["CreatedOnUTCLeft"].Position + 1).Width  = 20;
                    sheet.Column(table.Columns["CreatedOnUTCRight"].Position + 1).Width = 20;

                    ExcelAddress cfAddressDifference = new ExcelAddress(LIST_SHEET_START_TABLE_AT + 1, table.Columns["Difference"].Position + 1, sheet.Dimension.Rows, table.Columns["Difference"].Position + 1);
                    var          cfUserExperience    = sheet.ConditionalFormatting.AddEqual(cfAddressDifference);
                    cfUserExperience.Style.Font.Color.Color           = Color.Black;
                    cfUserExperience.Style.Fill.BackgroundColor.Color = colorDifferent;
                    cfUserExperience.Formula = String.Format(@"=""{0}""", DIFFERENCE_DIFFERENT);

                    cfUserExperience = sheet.ConditionalFormatting.AddEqual(cfAddressDifference);
                    cfUserExperience.Style.Font.Color.Color           = Color.Black;
                    cfUserExperience.Style.Fill.BackgroundColor.Color = colorExtra;
                    cfUserExperience.Formula = String.Format(@"=""{0}""", DIFFERENCE_EXTRA);

                    cfUserExperience = sheet.ConditionalFormatting.AddEqual(cfAddressDifference);
                    cfUserExperience.Style.Font.Color.Color           = Color.Black;
                    cfUserExperience.Style.Fill.BackgroundColor.Color = colorMissing;
                    cfUserExperience.Formula = String.Format(@"=""{0}""", DIFFERENCE_MISSING);

                    sheet = excelReport.Workbook.Worksheets[SHEET_DIFFERENCES_TYPE];
                    ExcelPivotTable pivot = sheet.PivotTables.Add(sheet.Cells[PIVOT_SHEET_START_PIVOT_AT + PIVOT_SHEET_CHART_HEIGHT, 1], range, PIVOT_DIFFERENCES_TYPE);
                    setDefaultPivotTableSettings(pivot);
                    addRowFieldToPivot(pivot, "ObjectType", eSortType.Ascending);
                    addRowFieldToPivot(pivot, "ObjectName", eSortType.Ascending);
                    addRowFieldToPivot(pivot, "Privilege", eSortType.Ascending);
                    addRowFieldToPivot(pivot, "GrantedTo", eSortType.Ascending);
                    addRowFieldToPivot(pivot, "DifferenceDetails", eSortType.Ascending);
                    addColumnFieldToPivot(pivot, "Difference", eSortType.Ascending);
                    addDataFieldToPivot(pivot, "UniqueIdentifier", DataFieldFunctions.Count, "NumDifferences");

                    sheet.Column(1).Width = 20;
                    sheet.Column(2).Width = 20;
                    sheet.Column(3).Width = 20;
                    sheet.Column(4).Width = 20;
                    sheet.Column(5).Width = 20;

                    ExcelChart chart = sheet.Drawings.AddChart(GRAPH_DIFFERENCES_TYPE, eChartType.ColumnStacked, pivot);
                    chart.SetPosition(2, 0, 0, 0);
                    chart.SetSize(800, 300);
                }

                #endregion

                #region Objects / Roles Differences sheet

                // Build the table
                // Object       | Role 1    | Role 2    | ...   | Role N
                // -----------------------------------------------------
                // DB1          | +U, O     |           |       | +U
                // DB2          | -U, O     | ~U        |       |
                // DB2          | -U, O     | U         |       |

                List <GrantDifference> grantDifferencesList = FileIOHelper.ReadListFromCSVFile <GrantDifference>(FilePathMap.Report_RoleGrant_Differences_FilePath(), new GrantDifferenceMap());
                if (grantDifferencesList != null)
                {
                    var groupObjectTypesGrouped = grantDifferencesList.GroupBy(g => g.ObjectType);

                    foreach (var groupObjectType in groupObjectTypesGrouped)
                    {
                        string objectType = groupObjectType.Key;
                        Dictionary <string, int> roleToHeaderMapping = new Dictionary <string, int>();

                        loggerConsole.Info("Processing grants differences for {0}", objectType);

                        List <GrantDifference> grantDifferencesOfObjectTypeList = groupObjectType.ToList();

                        sheet = excelReport.Workbook.Worksheets.Add(getShortenedNameForExcelSheet(String.Format(SHEET_GRANT_DIFFERENCES_PER_OBJECT_TYPE, objectType)));
                        sheet.Cells[1, 1].Value     = "Table of Contents";
                        sheet.Cells[1, 2].Formula   = String.Format(@"=HYPERLINK(""#'{0}'!A1"", ""<Go>"")", SHEET_TOC);
                        sheet.Cells[1, 2].StyleName = "HyperLinkStyle";
                        sheet.Cells[2, 1].Value     = "Left";
                        sheet.Cells[2, 2].Value     = programOptions.LeftReportFolderPath;
                        sheet.Cells[3, 1].Value     = "Right";
                        sheet.Cells[3, 2].Value     = programOptions.RightReportFolderPath;
                        sheet.Cells[4, 1].Value     = "Type";
                        sheet.Cells[4, 2].Value     = objectType;
                        sheet.View.FreezePanes(LIST_SHEET_START_TABLE_AT + 2, 3);

                        logger.Info("{0} Sheet", sheet.Name);
                        loggerConsole.Info("{0} Sheet", sheet.Name);

                        int headerRowIndex       = LIST_SHEET_START_TABLE_AT + 1;
                        int roleColumnBeginIndex = 3;
                        int roleColumnMaxIndex   = roleColumnBeginIndex;

                        // Header row
                        sheet.Cells[headerRowIndex, 1].Value = "Full Name";
                        sheet.Cells[headerRowIndex, 2].Value = "Short Name";

                        int currentRowIndex = headerRowIndex;
                        currentRowIndex++;

                        var groupObjectNameGrouped = grantDifferencesOfObjectTypeList.GroupBy(g => g.ObjectName);
                        foreach (var groupObjectName in groupObjectNameGrouped)
                        {
                            GrantDifference grantDifferenceObjectToOperateOn = groupObjectName.First();
                            sheet.Cells[currentRowIndex, 1].Value = grantDifferenceObjectToOperateOn.ObjectName;
                            sheet.Cells[currentRowIndex, 2].Value = grantDifferenceObjectToOperateOn.EntityName;

                            List <GrantDifference> grantDifferencesOfThisObjectList = groupObjectName.ToList();
                            var grantsByRoleNameGroups = grantDifferencesOfThisObjectList.GroupBy(g => g.GrantedTo);
                            foreach (var grantsByRoleNameGroup in grantsByRoleNameGroups)
                            {
                                GrantDifference firstGrantDifference = grantsByRoleNameGroup.First();
                                int             thisRoleColumnIndex  = 0;
                                if (roleToHeaderMapping.ContainsKey(firstGrantDifference.GrantedTo) == false)
                                {
                                    // Add another Role to the header
                                    thisRoleColumnIndex = roleColumnMaxIndex;
                                    roleToHeaderMapping.Add(firstGrantDifference.GrantedTo, thisRoleColumnIndex);
                                    sheet.Cells[headerRowIndex, thisRoleColumnIndex].Value = firstGrantDifference.GrantedTo;
                                    roleColumnMaxIndex++;
                                }
                                else
                                {
                                    // Previously seen
                                    thisRoleColumnIndex = roleToHeaderMapping[firstGrantDifference.GrantedTo];
                                }
                                sheet.Cells[currentRowIndex, thisRoleColumnIndex].Value = grantsByRoleNameGroup.ToList().Count();
                                outputGrantDifferencestToCell(sheet.Cells[currentRowIndex, thisRoleColumnIndex], grantsByRoleNameGroup.ToList());
                            }

                            currentRowIndex++;
                        }

                        range = sheet.Cells[headerRowIndex, 1, sheet.Dimension.Rows, sheet.Dimension.Columns];
                        try
                        {
                            table = sheet.Tables.Add(range, getExcelTableOrSheetSafeString(String.Format(TABLE_GRANT_DIFFERENCES_PER_OBJECT_TYPE, objectType)));
                        }
                        catch (ArgumentException ex)
                        {
                            if (ex.Message == "Tablename is not unique")
                            {
                                table = sheet.Tables.Add(range, String.Format("{0}_1", getExcelTableOrSheetSafeString(String.Format(TABLE_GRANT_DIFFERENCES_PER_OBJECT_TYPE, objectType))));
                            }
                        }
                        table.ShowHeader = true;
                        table.TableStyle = TableStyles.Light18;
                        table.ShowFilter = true;
                        table.ShowTotal  = false;

                        sheet.Column(1).Width = 30;
                        sheet.Column(2).Width = 20;

                        // Make the column for permissions headers angled downwards 45 degrees
                        for (int i = roleColumnBeginIndex; i <= table.Columns.Count; i++)
                        {
                            sheet.Cells[headerRowIndex, i].Style.TextRotation = 135;
                            sheet.Column(i).Width = 7;
                        }

                        // Format the cells
                        ExcelRangeBase rangeToFormat = sheet.Cells[headerRowIndex + 1, 3, sheet.Dimension.Rows, sheet.Dimension.Columns];
                        rangeToFormat.StyleName = "ShortDifferencesStyle";

                        var cfMoreThanOne = sheet.ConditionalFormatting.AddContainsText(rangeToFormat);
                        cfMoreThanOne.Style.Font.Color.Color           = Color.Black;
                        cfMoreThanOne.Style.Fill.BackgroundColor.Color = Color.MediumOrchid;
                        cfMoreThanOne.Text       = "-and-";
                        cfMoreThanOne.StopIfTrue = true;

                        var cfMissing = sheet.ConditionalFormatting.AddContainsText(rangeToFormat);
                        cfMissing.Style.Font.Color.Color           = Color.Black;
                        cfMissing.Style.Fill.BackgroundColor.Color = colorMissing;
                        cfMissing.Text = "<<";

                        var cfExtra = sheet.ConditionalFormatting.AddContainsText(rangeToFormat);
                        cfExtra.Style.Font.Color.Color           = Color.Black;
                        cfExtra.Style.Fill.BackgroundColor.Color = colorExtra;
                        cfExtra.Text = ">>";

                        var cfDifferent = sheet.ConditionalFormatting.AddContainsText(rangeToFormat);
                        cfDifferent.Style.Font.Color.Color           = Color.Black;
                        cfDifferent.Style.Fill.BackgroundColor.Color = colorDifferent;
                        cfDifferent.Text = "~~";

                        logger.Info("{0} Sheet ({1} rows)", sheet.Name, sheet.Dimension.Rows);
                        loggerConsole.Info("{0} Sheet ({1} rows)", sheet.Name, sheet.Dimension.Rows);
                    }
                }

                #endregion

                #region TOC sheet

                // TOC sheet again
                sheet = excelReport.Workbook.Worksheets[SHEET_TOC];
                fillTableOfContentsSheet(sheet, excelReport);

                #endregion

                #region Save file

                string reportFilePath = FilePathMap.GrantsDifferencesExcelReportFilePath();

                logger.Info("Saving Excel report {0}", reportFilePath);
                loggerConsole.Info("Saving Excel report {0}", reportFilePath);

                try
                {
                    // Save full report Excel files
                    excelReport.SaveAs(new FileInfo(reportFilePath));
                }
                catch (InvalidOperationException ex)
                {
                    logger.Warn("Unable to save Excel file {0}", reportFilePath);
                    logger.Warn(ex);
                    loggerConsole.Warn("Unable to save Excel file {0}", reportFilePath);
                }

                #endregion

                return(true);
            }
            catch (Exception ex)
            {
                logger.Error(ex);
                loggerConsole.Error(ex);

                return(false);
            }
            finally
            {
                stopWatch.Stop();

                this.DisplayJobStepEndedStatus(programOptions, stopWatch);

                stepTimingFunction.EndTime    = DateTime.Now;
                stepTimingFunction.Duration   = stopWatch.Elapsed;
                stepTimingFunction.DurationMS = stopWatch.ElapsedMilliseconds;

                List <StepTiming> stepTimings = new List <StepTiming>(1);
                stepTimings.Add(stepTimingFunction);
                FileIOHelper.WriteListToCSVFile(stepTimings, new StepTimingReportMap(), FilePathMap.StepTimingReportFilePath(), true);
            }
        }
コード例 #2
0
        public override bool Execute(ProgramOptions programOptions)
        {
            Stopwatch stopWatch = new Stopwatch();

            stopWatch.Start();

            StepTiming stepTimingFunction = new StepTiming();

            stepTimingFunction.JobFileName = programOptions.ReportJobFilePath;
            stepTimingFunction.StepName    = programOptions.ReportJob.Status.ToString();
            stepTimingFunction.StepID      = (int)programOptions.ReportJob.Status;
            stepTimingFunction.StartTime   = DateTime.Now;
            stepTimingFunction.NumEntities = 0;

            this.DisplayJobStepStartingStatus(programOptions);

            this.FilePathMap = new FilePathMap(programOptions);

            ProgramOptions programOptionsLeft = new ProgramOptions();

            programOptionsLeft.ReportFolderPath = programOptions.LeftReportFolderPath;
            FilePathMap filePathMapLeft = new FilePathMap(programOptionsLeft);

            ProgramOptions programOptionsRight = new ProgramOptions();

            programOptionsRight.ReportFolderPath = programOptions.RightReportFolderPath;
            FilePathMap filePathMapRight = new FilePathMap(programOptionsRight);

            try
            {
                // Load all the grants from both sides
                loggerConsole.Info("Loading Grants for Left side {0}", programOptionsLeft.ReportFolderPath);
                List <Grant> grantsAllLeftList = FileIOHelper.ReadListFromCSVFile <Grant>(filePathMapLeft.Report_RoleGrant_FilePath(), new GrantMap());
                if (grantsAllLeftList == null || grantsAllLeftList.Count == 0)
                {
                    loggerConsole.Warn("No grants to compare on the Left side");
                    return(false);
                }

                loggerConsole.Info("Loading Grants for Right side {0}", programOptionsRight.ReportFolderPath);
                List <Grant> grantsAllRightList = FileIOHelper.ReadListFromCSVFile <Grant>(filePathMapRight.Report_RoleGrant_FilePath(), new GrantMap());
                if (grantsAllRightList == null || grantsAllRightList.Count == 0)
                {
                    loggerConsole.Warn("No grants to compare with on the Right side");
                    return(false);
                }

                // If we got here, we potentially have a large list of Grants on both sides all in memory, begin comparison
                logger.Trace("Left side list of Grants {0} items", grantsAllLeftList.Count);
                logger.Trace("Right side list of Grants {0} items", grantsAllRightList.Count);
                loggerConsole.Info("Left side {0} grants <-> Right side {1} Grants", grantsAllLeftList.Count, grantsAllRightList.Count);

                // Avoid duplicate grants by Grouping first
                // Really could only happen when converting from the spreadsheets, not with any dumps from Snowflake
                Dictionary <string, Grant> grantsAllLeftDict = new Dictionary <string, Grant>(grantsAllLeftList.Count);
                foreach (Grant grant in grantsAllLeftList)
                {
                    if (grantsAllLeftDict.ContainsKey(grant.UniqueIdentifier) == false)
                    {
                        grantsAllLeftDict.Add(grant.UniqueIdentifier, grant);
                    }
                }
                Dictionary <string, Grant> grantsAllRightDict = new Dictionary <string, Grant>(grantsAllRightList.Count);
                foreach (Grant grant in grantsAllRightList)
                {
                    if (grantsAllRightDict.ContainsKey(grant.UniqueIdentifier) == false)
                    {
                        grantsAllRightDict.Add(grant.UniqueIdentifier, grant);
                    }
                }

                // Here is what we get in the reference and difference lists
                // List             List
                // Reference        Difference      Action
                // AAA              AAA             Compare items
                // BBB                              item in Difference is MISSING
                //                  CCC             item in Difference is EXTRA
                // The columns of Grants are:
                //      Privilege,ObjectType,ObjectName,GrantedTo,DBName,SchemaName,EntityName,GrantedBy,WithGrantOption,CreatedOn,CreatedOnUTC
                // Out of those, the identifying combination of a Grant is:
                //      Privilege,ObjectType,ObjectName,GrantedTo
                // And it is stored in Grant.UniqueIdentifier

                // Assume 1% differences
                List <GrantDifference> grantDifferencesList = new List <GrantDifference>(grantsAllLeftList.Count / 100);

                loggerConsole.Info("Comparing Left side -> Right Side");
                int j = 0;

                // First loop through Reference list looking for matches
                foreach (Grant grantLeft in grantsAllLeftDict.Values)
                {
                    Grant grantRight = null;
                    if (grantsAllRightDict.TryGetValue(grantLeft.UniqueIdentifier, out grantRight) == true)
                    {
                        // Found matching entity AAA. Let's compare them against each other
                        List <string> differentPropertiesList = new List <string>(2);

                        // Only compare if GrantedBy is non-empty
                        if ((grantLeft.GrantedBy.Length > 0 && grantRight.GrantedBy.Length > 0) && grantLeft.GrantedBy != grantRight.GrantedBy)
                        {
                            differentPropertiesList.Add("GrantedBy");
                        }

                        // Only compare of CreatedOn is a real date
                        if ((grantLeft.CreatedOn != DateTime.MinValue && grantRight.CreatedOn != DateTime.MinValue) && grantLeft.CreatedOn != grantRight.CreatedOn)
                        {
                            // Sometimes the CreatedOn is only different just a tiny little bit like here:
                            // CreatedOnUTCLeft	                CreatedOnUTCRight
                            // 2020-12-01T01:12:16.1360000Z     2020-12-01T01:12:16.3940000Z
                            // As you can see it is different only in milliseconds. Must be an FDB thing
                            // Going to ignore sub-second differences
                            TimeSpan timeDifference = grantLeft.CreatedOn - grantRight.CreatedOn;
                            if (Math.Abs(timeDifference.TotalSeconds) > 1)
                            {
                                differentPropertiesList.Add("CreatedOn");
                            }
                        }
                        if (grantLeft.WithGrantOption != grantRight.WithGrantOption)
                        {
                            differentPropertiesList.Add("WithGrantOption");
                        }

                        if (differentPropertiesList.Count > 0)
                        {
                            GrantDifference grantDifference = new GrantDifference();
                            grantDifference.UniqueIdentifier = grantRight.UniqueIdentifier;
                            grantDifference.Privilege        = grantRight.Privilege;
                            grantDifference.ObjectType       = grantRight.ObjectType;
                            grantDifference.ObjectName       = grantRight.ObjectName;
                            grantDifference.GrantedTo        = grantRight.GrantedTo;
                            grantDifference.DBName           = grantRight.DBName;
                            grantDifference.SchemaName       = grantRight.SchemaName;
                            grantDifference.EntityName       = grantRight.EntityName;

                            grantDifference.ReportLeft        = programOptions.LeftReportFolderPath;
                            grantDifference.ReportRight       = programOptions.RightReportFolderPath;
                            grantDifference.Difference        = DIFFERENCE_DIFFERENT;
                            grantDifference.DifferenceDetails = String.Join(',', differentPropertiesList.ToArray());

                            grantDifference.GrantedByLeft        = grantLeft.GrantedBy;
                            grantDifference.CreatedOnUTCLeft     = grantLeft.CreatedOnUTC;
                            grantDifference.WithGrantOptionLeft  = grantLeft.WithGrantOption;
                            grantDifference.GrantedByRight       = grantRight.GrantedBy;
                            grantDifference.CreatedOnUTCRight    = grantRight.CreatedOnUTC;
                            grantDifference.WithGrantOptionRight = grantRight.WithGrantOption;

                            grantDifferencesList.Add(grantDifference);
                        }

                        // Remove this object as already considered
                        grantsAllRightDict[grantRight.UniqueIdentifier] = null;
                    }
                    else
                    {
                        // No match. This must be entity BBB, where item in Difference is MISSING
                        GrantDifference grantDifference = new GrantDifference();
                        grantDifference.UniqueIdentifier = grantLeft.UniqueIdentifier;
                        grantDifference.Privilege        = grantLeft.Privilege;
                        grantDifference.ObjectType       = grantLeft.ObjectType;
                        grantDifference.ObjectName       = grantLeft.ObjectName;
                        grantDifference.GrantedTo        = grantLeft.GrantedTo;
                        grantDifference.DBName           = grantLeft.DBName;
                        grantDifference.SchemaName       = grantLeft.SchemaName;
                        grantDifference.EntityName       = grantLeft.EntityName;

                        grantDifference.ReportLeft        = programOptions.LeftReportFolderPath;
                        grantDifference.ReportRight       = programOptions.RightReportFolderPath;
                        grantDifference.Difference        = DIFFERENCE_MISSING;
                        grantDifference.DifferenceDetails = PROPERTY_ENTIRE_OBJECT;

                        grantDifference.GrantedByLeft       = grantLeft.GrantedBy;
                        grantDifference.CreatedOnUTCLeft    = grantLeft.CreatedOnUTC;
                        grantDifference.WithGrantOptionLeft = grantLeft.WithGrantOption;

                        grantDifferencesList.Add(grantDifference);
                    }

                    // Remove this object as already considered
                    grantsAllLeftDict[grantLeft.UniqueIdentifier] = null;

                    j++;
                    if (j % 1000 == 0)
                    {
                        Console.Write("{0}.", j);
                    }
                }

                Console.WriteLine("Processed {0} comparisons", grantsAllLeftDict.Count);

                loggerConsole.Info("Comparing Right side -> Left Side");
                j = 0;
                foreach (Grant grantRight in grantsAllRightDict.Values)
                {
                    if (grantRight != null)
                    {
                        GrantDifference grantDifference = new GrantDifference();
                        grantDifference.UniqueIdentifier = grantRight.UniqueIdentifier;
                        grantDifference.Privilege        = grantRight.Privilege;
                        grantDifference.ObjectType       = grantRight.ObjectType;
                        grantDifference.ObjectName       = grantRight.ObjectName;
                        grantDifference.GrantedTo        = grantRight.GrantedTo;
                        grantDifference.DBName           = grantRight.DBName;
                        grantDifference.SchemaName       = grantRight.SchemaName;
                        grantDifference.EntityName       = grantRight.EntityName;

                        grantDifference.ReportLeft        = programOptions.LeftReportFolderPath;
                        grantDifference.ReportRight       = programOptions.RightReportFolderPath;
                        grantDifference.Difference        = DIFFERENCE_EXTRA;
                        grantDifference.DifferenceDetails = PROPERTY_ENTIRE_OBJECT;

                        grantDifference.GrantedByRight       = grantRight.GrantedBy;
                        grantDifference.CreatedOnUTCRight    = grantRight.CreatedOnUTC;
                        grantDifference.WithGrantOptionRight = grantRight.WithGrantOption;

                        grantDifferencesList.Add(grantDifference);
                    }

                    j++;
                    if (j % 1000 == 0)
                    {
                        Console.Write("{0}.", j);
                    }
                }

                loggerConsole.Info("Found {0} differences", grantDifferencesList.Count);

                FileIOHelper.WriteListToCSVFile <GrantDifference>(grantDifferencesList, new GrantDifferenceMap(), FilePathMap.Report_RoleGrant_Differences_FilePath());

                return(true);
            }
            catch (Exception ex)
            {
                logger.Error(ex);
                loggerConsole.Error(ex);

                return(false);
            }
            finally
            {
                stopWatch.Stop();

                this.DisplayJobStepEndedStatus(programOptions, stopWatch);

                stepTimingFunction.EndTime    = DateTime.Now;
                stepTimingFunction.Duration   = stopWatch.Elapsed;
                stepTimingFunction.DurationMS = stopWatch.ElapsedMilliseconds;

                List <StepTiming> stepTimings = new List <StepTiming>(1);
                stepTimings.Add(stepTimingFunction);
                FileIOHelper.WriteListToCSVFile(stepTimings, new StepTimingReportMap(), FilePathMap.StepTimingReportFilePath(), true);
            }
        }