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); } }
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); } }