示例#1
0
        static void Main(string[] args)
        {
            // parse command line
            var options = new Options();

            if (args.Length == 0)
            {
                Console.WriteLine(options.GetUsage());
                Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
            }

            var parser = new Parser(settings =>
            {
                settings.MutuallyExclusive = true;
                settings.HelpWriter        = Parser.Default.Settings.HelpWriter;
                settings.CaseSensitive     = false;
            });

            if (!parser.ParseArguments(args, options))
            {
                Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
            }

            System.Collections.Generic.List <Mode> lstmode = options.GetAllOptions(args);

            // perform additional validation
            if (!String.IsNullOrEmpty(options.ClientID))
            {
                if (String.IsNullOrEmpty(options.ClientSecret))
                {
                    Console.WriteLine("If you specify a client id you also need to specify a client secret");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }
            if (!String.IsNullOrEmpty(options.ClientSecret))
            {
                if (String.IsNullOrEmpty(options.ClientID))
                {
                    Console.WriteLine("If you specify a client secret you also need to specify a client id");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }

            if (!String.IsNullOrEmpty(options.User))
            {
                if (String.IsNullOrEmpty(options.Password))
                {
                    Console.WriteLine("If you specify a user you also need to specify a password");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }
            if (!String.IsNullOrEmpty(options.Password))
            {
                if (String.IsNullOrEmpty(options.User))
                {
                    Console.WriteLine("If you specify a password you also need to specify a user");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }
            if (!String.IsNullOrEmpty(options.File))
            {
                if (!System.IO.File.Exists(options.File))
                {
                    Console.WriteLine("Failed to find csv file with urls. Please check file path provided.");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }

            // Better support for running/testing the tool when SharePoint site certificate is not trusted on the box running the scan
            System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) =>
            {
                return(true);
            };

            // Instantiate scan job
            UIExperienceScanner uiExpScanner = new UIExperienceScanner()
            {
                UseThreading    = true,
                Modes           = lstmode,
                MaximumThreads  = options.Threads,
                TenantAdminSite = options.TenantAdminSite,
                OutputFolder    = DateTime.Now.Ticks.ToString(),
                Separator       = options.Separator,
                ExcludeListsOnlyBlockedByOobReasons = options.ExcludeListsOnlyBlockedByOobReasons
            };

            // temp debug
            //uiExpScanner.UseThreading = false;

            // set scan creds
            if (options.UsesAppOnly())
            {
                uiExpScanner.UseAppOnlyAuthentication(options.ClientID, options.ClientSecret);
            }
            else if (options.UsesCredentials())
            {
                uiExpScanner.UseOffice365Authentication(options.User, options.Password);
            }

            uiExpScanner.ExcludeOD4B = !options.IncludeOD4B;

            // set scan urls
            if (!String.IsNullOrEmpty(options.Tenant))
            {
                uiExpScanner.AddSite(string.Format("https://{0}.sharepoint.com/*", options.Tenant));
                uiExpScanner.AddSite(string.Format("https://{0}-my.sharepoint.com/*", options.Tenant));
            }
            else if (options.Urls != null && options.Urls.Count > 0)
            {
                foreach (var url in options.Urls)
                {
                    uiExpScanner.AddSite(url);
                }
            }
            else
            {
                foreach (var row in LoadSitesFromCsv(options.File, options.Separator.ToCharArray().First()))
                {
                    uiExpScanner.AddSite(row[0]); //first column in the row contains url
                }
            }

            DateTime start = DateTime.Now;

            Console.WriteLine("=====================================================");
            Console.WriteLine("Scanning is starting...{0}", start.ToString());
            Console.WriteLine("=====================================================");
            Console.WriteLine("Building a list of site collections...");

            // Launch the job
            uiExpScanner.Run();

            // Dump scan results
            Console.WriteLine("=====================================================");
            Console.WriteLine("Scanning is done...now dump the results to a CSV file");
            Console.WriteLine("=====================================================");
            System.IO.Directory.CreateDirectory(uiExpScanner.OutputFolder);
            string outputfile = null;

            string[] outputHeaders = null;

            #region Modern List blocking reports
            if (lstmode.Contains(Mode.Scan) || lstmode.Contains(Mode.BlockedLists))
            {
                // output summary report
                outputfile = string.Format("{0}\\ModernListBlocked.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting list scan results to {0}", outputfile);
                outputHeaders = new string[] { "Url", "Site Url", "Site Collection Url", "List Title", "Only blocked by OOB reasons",
                                               "Blocked at site level", "Blocked at web level", "Blocked at list level", "List page render type", "List experience", "Blocked by not being able to load Page", "Blocked by not being able to load page exception",
                                               "Blocked by managed metadata navigation", "Blocked by view type", "View type", "Blocked by list base template", "List base template",
                                               "Blocked by zero or multiple web parts", "Blocked by JSLink", "JSLink", "Blocked by XslLink", "XslLink", "Blocked by Xsl",
                                               "Blocked by JSLink field", "JSLink fields", "Blocked by business data field", "Business data fields", "Blocked by task outcome field", "Task outcome fields",
                                               "Blocked by publishingField", "Publishing fields", "Blocked by geo location field", "Geo location fields", "Blocked by list custom action", "List custom actions" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));

                foreach (var list in uiExpScanner.ListResults)
                {
                    if (list.Value.BlockedByNotBeingAbleToLoadPageException == null)
                    {
                        list.Value.BlockedByNotBeingAbleToLoadPageException = "";
                    }

                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n",
                                                                           string.Join(options.Separator, ToCsv(list.Key.Substring(36)), ToCsv(list.Value.SiteUrl), ToCsv(list.Value.SiteColUrl), ToCsv(list.Value.ListTitle), list.Value.OnlyBlockedByOOBReasons,
                                                                                       list.Value.BlockedAtSiteLevel, list.Value.BlockedAtWebLevel, list.Value.BlockedAtListLevel, list.Value.PageRenderType, list.Value.ListExperience, list.Value.BlockedByNotBeingAbleToLoadPage, ToCsv(list.Value.BlockedByNotBeingAbleToLoadPageException),
                                                                                       list.Value.XsltViewWebPartCompatibility.BlockedByManagedMetadataNavFeature, list.Value.XsltViewWebPartCompatibility.BlockedByViewType, ToCsv(list.Value.XsltViewWebPartCompatibility.ViewType), list.Value.XsltViewWebPartCompatibility.BlockedByListBaseTemplate, list.Value.XsltViewWebPartCompatibility.ListBaseTemplate,
                                                                                       list.Value.BlockedByZeroOrMultipleWebParts, list.Value.XsltViewWebPartCompatibility.BlockedByJSLink, ToCsv(list.Value.XsltViewWebPartCompatibility.JSLink), list.Value.XsltViewWebPartCompatibility.BlockedByXslLink, ToCsv(list.Value.XsltViewWebPartCompatibility.XslLink), list.Value.XsltViewWebPartCompatibility.BlockedByXsl,
                                                                                       list.Value.XsltViewWebPartCompatibility.BlockedByJSLinkField, ToCsv(list.Value.XsltViewWebPartCompatibility.JSLinkFields), list.Value.XsltViewWebPartCompatibility.BlockedByBusinessDataField, ToCsv(list.Value.XsltViewWebPartCompatibility.BusinessDataFields), list.Value.XsltViewWebPartCompatibility.BlockedByTaskOutcomeField, ToCsv(list.Value.XsltViewWebPartCompatibility.TaskOutcomeFields),
                                                                                       list.Value.XsltViewWebPartCompatibility.BlockedByPublishingField, ToCsv(list.Value.XsltViewWebPartCompatibility.PublishingFields), list.Value.XsltViewWebPartCompatibility.BlockedByGeoLocationField, ToCsv(list.Value.XsltViewWebPartCompatibility.GeoLocationFields), list.Value.XsltViewWebPartCompatibility.BlockedByListCustomAction, ToCsv(list.Value.XsltViewWebPartCompatibility.ListCustomActions))));
                }
            }
            #endregion

            #region Ignored customisations reports
            if (lstmode.Contains(Mode.Scan) || lstmode.Contains(Mode.IgnoredCustomizations))
            {
                // output detailed reports
                // custom actions
                outputfile = string.Format("{0}\\IgnoredCustomizations_CustomAction.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting list custom action scan results to {0}", outputfile);
                outputHeaders = new string[] { "Url", "Site Url", "Site Collection Url", "List Name", "Title", "Name", "Location", "RegistrationType", "RegistrationId", "Reason", /*"ImageMaps",*/ "ScriptBlock", "ScriptSrc", "CommandActions" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                CustomActionsResult item1;
                while (uiExpScanner.CustomActionScanResults.TryPop(out item1))
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, ToCsv(item1.Url), ToCsv(item1.SiteUrl), ToCsv(item1.SiteColUrl), ToCsv(item1.ListTitle),
                                                                                                  ToCsv(item1.Title), ToCsv(item1.Name), ToCsv(item1.Location), item1.RegistrationType,
                                                                                                  ToCsv(item1.RegistrationId), ToCsv(item1.Problem), /*item1.ImageMaps,*/
                                                                                                  ToCsv(item1.ScriptBlock),
                                                                                                  ToCsv(item1.ScriptSrc),
                                                                                                  item1.CommandActions)));
                }

                // alternate css
                outputfile = string.Format("{0}\\IgnoredCustomizations_AlternateCSS.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting alternate css scan to {0}", outputfile);
                outputHeaders = new string[] { "Url", "Site Url", "Site Collection Url", "Web Template", "AlternateCSS" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                AlternateCSSResult item2;
                while (uiExpScanner.AlternateCSSResults.TryPop(out item2))
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, ToCsv(item2.Url), ToCsv(item2.SiteUrl), ToCsv(item2.SiteColUrl), ToCsv(item2.WebTemplate), ToCsv(item2.AlternateCSS))));
                }

                // master pages
                outputfile = string.Format("{0}\\IgnoredCustomizations_MasterPage.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting master page scan to {0}", outputfile);
                outputHeaders = new string[] { "Url", "Site Url", "Site Collection Url", "Web Template", "MasterPage", "Custom MasterPage" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                MasterPageResult item3;
                while (uiExpScanner.MasterPageResults.TryPop(out item3))
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, ToCsv(item3.Url), ToCsv(item3.SiteUrl), ToCsv(item3.SiteColUrl), ToCsv(item3.WebTemplate), ToCsv(item3.MasterPage), ToCsv(item3.CustomMasterPage))));
                }

                // output summary report
                outputfile = string.Format("{0}\\IgnoredCustomizations.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting ignored customization results to {0}", outputfile);
                outputHeaders = new string[] { "Url", "Site Url", "Site Collection Url", "Ignored MasterPage", "Ignored Custom MasterPage", "Ignored AlternateCSS", "Ignored Custom Action" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));

                foreach (var item in uiExpScanner.CustomizationResults)
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, ToCsv(item.Key), ToCsv(item.Value.SiteUrl), ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.IgnoredMasterPage), ToCsv(item.Value.IgnoredCustomMasterPage), ToCsv(item.Value.IgnoredAlternateCSS), item.Value.IgnoredCustomAction)));
                }
            }
            #endregion

            #region Modern page blocking report
            if (lstmode.Contains(Mode.Scan) || lstmode.Contains(Mode.BlockedPages))
            {
                // output summary report
                outputfile = string.Format("{0}\\ModernPagesBlocked.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting modern page blocked scan to {0}", outputfile);
                outputHeaders = new string[] { "Url", "Site Url", "Site Collection Url", "Web Template", "Modern page feature was enabled", "Blocked via disabled modern page web feature" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));

                foreach (var item in uiExpScanner.PageResults)
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, ToCsv(item.Key.Substring(36)), ToCsv(item.Value.SiteUrl), ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.WebTemplate), item.Value.WasEnabledBySPO, item.Value.BlockedViaDisabledModernPageWebFeature)));
                }
            }
            #endregion

            #region Error reporting
            if (uiExpScanner.UIExpScanErrors.Count > 0)
            {
                string errorfile = string.Format("{0}\\errors.csv", uiExpScanner.OutputFolder);
                // Dump scan errors
                Console.WriteLine("Outputting errors to {0}", errorfile);
                outputHeaders = new string[] { "Site Url", "Site Collection Url", "Error" };
                System.IO.File.AppendAllText(errorfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                UIExperienceScanError error;
                while (uiExpScanner.UIExpScanErrors.TryPop(out error))
                {
                    System.IO.File.AppendAllText(errorfile, string.Format("{0}\r\n", string.Join(options.Separator, ToCsv(error.SiteURL), ToCsv(error.SiteColUrl), ToCsv(error.Error))));
                }
            }
            #endregion

            #region Scanner data
            if (uiExpScanner.ScannedSites > 0)
            {
                outputfile = string.Format("{0}\\ScannerSummary.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting information over the done scan to {0}", outputfile);
                outputHeaders = new string[] { "Site collections scanned", "Webs scanned", "List scanned", "Scan duration", "Scanner version" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));

                Assembly        assembly = System.Reflection.Assembly.GetExecutingAssembly();
                FileVersionInfo fvi      = FileVersionInfo.GetVersionInfo(assembly.Location);
                string          version  = fvi.FileVersion;
                TimeSpan        ts       = DateTime.Now.Subtract(uiExpScanner.StartTime);
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, uiExpScanner.ScannedSites, uiExpScanner.ScannedWebs, uiExpScanner.ScannedLists, $"{ts.Days} days, {ts.Hours} hours, {ts.Minutes} minutes and {ts.Seconds} seconds", version)));
            }
            #endregion

            Console.WriteLine("=====================================================");
            Console.WriteLine("All done. Took {0} for {1} sites", (DateTime.Now - start).ToString(), uiExpScanner.ScannedSites);
            Console.WriteLine("=====================================================");
        }
示例#2
0
        static void Main(string[] args)
        {
            // parse command line
            var options = new Options();

            if (args.Length == 0)
            {
                Console.WriteLine(options.GetUsage());
                Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
            }

            var parser = new Parser(settings =>
            {
                settings.MutuallyExclusive = true;
                settings.HelpWriter        = Parser.Default.Settings.HelpWriter;
                settings.CaseSensitive     = false;
            });

            if (!parser.ParseArguments(args, options))
            {
                Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
            }

            System.Collections.Generic.List <Mode> lstmode = options.GetAllOptions(args);

            // perform additional validation
            if (!String.IsNullOrEmpty(options.ClientID))
            {
                if (String.IsNullOrEmpty(options.ClientSecret))
                {
                    Console.WriteLine("If you specify a client id you also need to specify a client secret");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }
            if (!String.IsNullOrEmpty(options.ClientSecret))
            {
                if (String.IsNullOrEmpty(options.ClientID))
                {
                    Console.WriteLine("If you specify a client secret you also need to specify a client id");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }

            if (!String.IsNullOrEmpty(options.User))
            {
                if (String.IsNullOrEmpty(options.Password))
                {
                    Console.WriteLine("If you specify a user you also need to specify a password");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }
            if (!String.IsNullOrEmpty(options.Password))
            {
                if (String.IsNullOrEmpty(options.User))
                {
                    Console.WriteLine("If you specify a password you also need to specify a user");
                    Environment.Exit(CommandLine.Parser.DefaultExitCodeFail);
                }
            }

            // Better support for running/testing the tool when SharePoint site certificate is not trusted on the box running the scan
            System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) =>
            {
                return(true);
            };

            // Instantiate scan job
            UIExperienceScanner uiExpScanner = new UIExperienceScanner();

            uiExpScanner.UseThreading    = true;
            uiExpScanner.Modes           = lstmode;
            uiExpScanner.MaximumThreads  = options.Threads;
            uiExpScanner.TenantAdminSite = options.TenantAdminSite;
            uiExpScanner.OutputFolder    = DateTime.Now.Ticks.ToString();
            uiExpScanner.Verbose         = options.Verbose;
            uiExpScanner.Separator       = options.Separator;

            // temp debug
            //uiExpScanner.UseThreading = false;

            // set scan creds
            if (options.UsesAppOnly())
            {
                uiExpScanner.UseAppOnlyAuthentication(options.ClientID, options.ClientSecret);
            }
            else if (options.UsesCredentials())
            {
                uiExpScanner.UseOffice365Authentication(options.User, options.Password);
            }

            uiExpScanner.ExcludeOD4B = options.ExcludeOD4B;

            // set scan urls
            if (!String.IsNullOrEmpty(options.Tenant))
            {
                uiExpScanner.AddSite(string.Format("https://{0}.sharepoint.com/*", options.Tenant));
                uiExpScanner.AddSite(string.Format("https://{0}-my.sharepoint.com/*", options.Tenant));
            }
            else
            {
                foreach (var url in options.Urls)
                {
                    uiExpScanner.AddSite(url);
                }
            }

            DateTime start = DateTime.Now;

            Console.WriteLine("=====================================================");
            Console.WriteLine("Scanning is starting...{0}", start.ToString());
            Console.WriteLine("=====================================================");
            Console.WriteLine("Building a list of site collections...");

            // Launch the job
            uiExpScanner.Run();

            // Dump scan results
            Console.WriteLine("=====================================================");
            Console.WriteLine("Scanning is done...now dump the results to a CSV file");
            Console.WriteLine("=====================================================");
            System.IO.Directory.CreateDirectory(uiExpScanner.OutputFolder);
            string outputfile = null;

            string[] outputHeaders = null;

            #region Modern List blocking reports
            if (lstmode.Contains(Mode.Scan) || lstmode.Contains(Mode.BlockedLists))
            {
                // output summary report
                outputfile = string.Format("{0}\\ModernListBlocked.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting list scan results to {0}", outputfile);
                outputHeaders = new string[] { "Url", "SiteURL", "List Title",
                                               "Blocked at site level", "Blocked at web level", "Blocked at list level", "List page render type", "List experience", "Blocked by not being able to load Page", "Blocked by not being able to load page exception",
                                               "Blocked by managed metadata navigation", "Blocked by view type", "View type", "Blocked by list base template", "List base template",
                                               "Blocked by zero or multiple web parts", "Blocked by JSLink", "JSLink", "Blocked by XslLink", "XslLink", "Blocked by Xsl",
                                               "Blocked by JSLink field", "JSLink fields", "Blocked by business data field", "Business data fields", "Blocked by task outcome field", "Task outcome fields",
                                               "Blocked by publishingField", "Publishing fields", "Blocked by geo location field", "Geo location fields", "Blocked by list custom action", "List custom actions" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));

                foreach (var list in uiExpScanner.ListResults)
                {
                    if (list.Value.BlockedByNotBeingAbleToLoadPageException == null)
                    {
                        list.Value.BlockedByNotBeingAbleToLoadPageException = "";
                    }

                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n",
                                                                           string.Join(options.Separator, $"\"{list.Key}\"", $"\"{list.Value.SiteUrl}\"", $"\"{list.Value.ListTitle.Replace("\"", "\"\"")}\"",
                                                                                       list.Value.BlockedAtSiteLevel, list.Value.BlockedAtWebLevel, list.Value.BlockedAtListLevel, list.Value.PageRenderType, list.Value.ListExperience, list.Value.BlockedByNotBeingAbleToLoadPage, $"\"{list.Value.BlockedByNotBeingAbleToLoadPageException.Trim().Replace("\r\n", string.Empty).Replace("\"", "\"\"")}\"",
                                                                                       list.Value.XsltViewWebPartCompatibility.BlockedByManagedMetadataNavFeature, list.Value.XsltViewWebPartCompatibility.BlockedByViewType, list.Value.XsltViewWebPartCompatibility.ViewType, list.Value.XsltViewWebPartCompatibility.BlockedByListBaseTemplate, list.Value.XsltViewWebPartCompatibility.ListBaseTemplate,
                                                                                       list.Value.BlockedByZeroOrMultipleWebParts, list.Value.XsltViewWebPartCompatibility.BlockedByJSLink, $"\"{list.Value.XsltViewWebPartCompatibility.JSLink}\"", list.Value.XsltViewWebPartCompatibility.BlockedByXslLink, $"\"{list.Value.XsltViewWebPartCompatibility.XslLink}\"", list.Value.XsltViewWebPartCompatibility.BlockedByXsl,
                                                                                       list.Value.XsltViewWebPartCompatibility.BlockedByJSLinkField, $"\"{list.Value.XsltViewWebPartCompatibility.JSLinkFields}\"", list.Value.XsltViewWebPartCompatibility.BlockedByBusinessDataField, $"\"{list.Value.XsltViewWebPartCompatibility.BusinessDataFields}\"", list.Value.XsltViewWebPartCompatibility.BlockedByTaskOutcomeField, $"\"{list.Value.XsltViewWebPartCompatibility.TaskOutcomeFields}\"",
                                                                                       list.Value.XsltViewWebPartCompatibility.BlockedByPublishingField, $"\"{list.Value.XsltViewWebPartCompatibility.PublishingFields}\"", list.Value.XsltViewWebPartCompatibility.BlockedByGeoLocationField, $"\"{list.Value.XsltViewWebPartCompatibility.GeoLocationFields}\"", list.Value.XsltViewWebPartCompatibility.BlockedByListCustomAction, $"\"{list.Value.XsltViewWebPartCompatibility.ListCustomActions}\"")));
                }
            }
            #endregion

            #region Ignored customisations reports
            if (lstmode.Contains(Mode.Scan) || lstmode.Contains(Mode.IgnoredCustomizations))
            {
                // output detailed reports
                // custom actions
                outputfile = string.Format("{0}\\IgnoredCustomizations_CustomAction.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting list custom action scan results to {0}", outputfile);
                outputHeaders = new string[] { "Url", "SiteURL", "List Name", "Title", "Name", "Location", "RegistrationType", "RegistrationId", "Reason", /*"ImageMaps",*/ "ScriptBlock", "ScriptSrc", "CommandActions" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                CustomActionsResult item1;
                while (uiExpScanner.CustomActionScanResults.TryPop(out item1))
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, $"\"{item1.Url}\"", $"\"{item1.SiteUrl}\"", $"\"{item1.ListTitle}\"",
                                                                                                  $"\"{item1.Title}\"", $"\"{item1.Name}\"", $"\"{item1.Location}\"", $"\"{item1.RegistrationType}\"",
                                                                                                  $"\"{item1.RegistrationId}\"", $"\"{item1.Problem}\"", /*item1.ImageMaps,*/
                                                                                                  $"\"{item1.ScriptBlock.Trim().Replace("\r\n", string.Empty).Replace("\"", "\"\"")}\"",
                                                                                                  $"\"{item1.ScriptSrc.Trim().Replace("\r\n", string.Empty).Replace("\"", "\"\"")}\"",
                                                                                                  item1.CommandActions)));
                }

                // alternate css
                outputfile = string.Format("{0}\\IgnoredCustomizations_AlternateCSS.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting alternate css scan to {0}", outputfile);
                outputHeaders = new string[] { "Url", "SiteURL", "AlternateCSS" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                AlternateCSSResult item2;
                while (uiExpScanner.AlternateCSSResults.TryPop(out item2))
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, $"\"{item2.Url}\"", $"\"{item2.SiteUrl}\"", $"\"{item2.AlternateCSS}\"")));
                }

                // master pages
                outputfile = string.Format("{0}\\IgnoredCustomizations_MasterPage.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting master page scan to {0}", outputfile);
                outputHeaders = new string[] { "Url", "SiteURL", "MasterPage", "Custom MasterPage" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                MasterPageResult item3;
                while (uiExpScanner.MasterPageResults.TryPop(out item3))
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, $"\"{item3.Url}\"", $"\"{item3.SiteUrl}\"", $"\"{item3.MasterPage}\"", $"\"{item3.CustomMasterPage}\"")));
                }

                // output summary report
                outputfile = string.Format("{0}\\IgnoredCustomizations.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting ignored customization results to {0}", outputfile);
                outputHeaders = new string[] { "Url", "SiteURL", "Ignored MasterPage", "Ignored Custom MasterPage", "Ignored AlternateCSS", "Ignored Custom Action" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));

                foreach (var item in uiExpScanner.CustomizationResults)
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, $"\"{item.Key}\"", $"\"{item.Value.SiteUrl}\"", $"\"{item.Value.IgnoredMasterPage}\"", $"\"{item.Value.IgnoredCustomMasterPage}\"", $"\"{item.Value.IgnoredAlternateCSS}\"", $"\"{item.Value.IgnoredCustomAction}\"")));
                }
            }
            #endregion

            #region Modern page blocking report
            if (lstmode.Contains(Mode.Scan) || lstmode.Contains(Mode.BlockedPages))
            {
                // output summary report
                outputfile = string.Format("{0}\\ModernPagesBlocked.csv", uiExpScanner.OutputFolder);
                Console.WriteLine("Outputting modern page blocked scan to {0}", outputfile);
                outputHeaders = new string[] { "Url", "SiteURL", "Blocked via disabled modern page web feature" };
                System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));

                foreach (var item in uiExpScanner.PageResults)
                {
                    System.IO.File.AppendAllText(outputfile, string.Format("{0}\r\n", string.Join(options.Separator, $"\"{item.Key}\"", $"\"{item.Value.SiteUrl}\"", $"\"{item.Value.BlockedViaDisabledModernPageWebFeature}\"")));
                }
            }
            #endregion

            #region Error reporting
            if (uiExpScanner.UIExpScanErrors.Count > 0)
            {
                string errorfile = string.Format("{0}\\errors.csv", uiExpScanner.OutputFolder);
                // Dump scan errors
                Console.WriteLine("Outputting errors to {0}", errorfile);
                outputHeaders = new string[] { "SiteURL", "Error" };
                System.IO.File.AppendAllText(errorfile, string.Format("{0}\r\n", string.Join(options.Separator, outputHeaders)));
                UIExperienceScanError error;
                while (uiExpScanner.UIExpScanErrors.TryPop(out error))
                {
                    System.IO.File.AppendAllText(errorfile, string.Format("{0}\r\n", string.Join(options.Separator, error.SiteURL, $"\"{error.Error.Trim().Replace("\r\n", string.Empty).Replace("\"", "\"\"")}\"")));
                }
            }
            #endregion

            Console.WriteLine("=====================================================");
            Console.WriteLine("All done. Took {0} for {1} sites", (DateTime.Now - start).ToString(), uiExpScanner.ScannedSites);
            Console.WriteLine("=====================================================");
        }