Example #1
0
        public override TimeSpan Analyze(ClientContext cc)
        {
            try
            {
                base.Analyze(cc);

                // Only scan when it's a valid publishing portal
                var pageCount = ContinueScanning(cc);
                if (pageCount > 0 || pageCount == -1)
                {
                    try
                    {
                        PublishingWebScanResult scanResult = new PublishingWebScanResult()
                        {
                            SiteColUrl     = this.SiteCollectionUrl,
                            SiteURL        = this.SiteUrl,
                            WebRelativeUrl = this.SiteUrl.Replace(this.SiteCollectionUrl, ""),
                            WebTemplate    = this.webScanResult.WebTemplate,
                            BrokenPermissionInheritance = this.webScanResult.BrokenPermissionInheritance,
                            PageCount         = pageCount == -1 ? 0 : pageCount,
                            SiteMasterPage    = this.webScanResult.CustomMasterPage,
                            SystemMasterPage  = this.webScanResult.MasterPage,
                            AlternateCSS      = this.webScanResult.AlternateCSS,
                            Admins            = this.siteScanResult.Admins,
                            Owners            = this.webScanResult.Owners,
                            UserCustomActions = new List <UserCustomActionResult>()
                        };

                        // User custom actions will play a role in complexity calculation
                        if (this.siteScanResult.SiteUserCustomActions != null && this.siteScanResult.SiteUserCustomActions.Count > 0)
                        {
                            scanResult.UserCustomActions.AddRange(this.siteScanResult.SiteUserCustomActions);
                        }
                        if (this.webScanResult.WebUserCustomActions != null && this.webScanResult.WebUserCustomActions.Count > 0)
                        {
                            scanResult.UserCustomActions.AddRange(this.webScanResult.WebUserCustomActions);
                        }

                        Web web = cc.Web;

                        // Load additional web properties
                        web.EnsureProperties(p => p.Language);
                        scanResult.Language = web.Language;

                        // PageLayouts handling
                        var availablePageLayouts = web.GetPropertyBagValueString(AvailablePageLayouts, "");
                        var defaultPageLayout    = web.GetPropertyBagValueString(DefaultPageLayout, "");

                        if (string.IsNullOrEmpty(availablePageLayouts))
                        {
                            scanResult.PageLayoutsConfiguration = "Any";
                        }
                        else if (availablePageLayouts.Equals("__inherit", StringComparison.InvariantCultureIgnoreCase))
                        {
                            scanResult.PageLayoutsConfiguration = "Inherit from parent";
                        }
                        else
                        {
                            scanResult.PageLayoutsConfiguration = "Defined list";

                            // Fill the defined list
                            var element = XElement.Parse(availablePageLayouts);
                            var nodes   = element.Descendants("layout");
                            if (nodes != null && nodes.Count() > 0)
                            {
                                string allowedPageLayouts = "";

                                foreach (var node in nodes)
                                {
                                    allowedPageLayouts = allowedPageLayouts + node.Attribute("url").Value.Replace("_catalogs/masterpage/", "") + ",";
                                }

                                allowedPageLayouts = allowedPageLayouts.TrimEnd(new char[] { ',' });

                                scanResult.AllowedPageLayouts = allowedPageLayouts;
                            }
                        }

                        if (!string.IsNullOrEmpty(defaultPageLayout))
                        {
                            var element = XElement.Parse(defaultPageLayout);
                            scanResult.DefaultPageLayout = element.Attribute("url").Value.Replace("_catalogs/masterpage/", "");
                        }

                        // Navigation
                        var navigationSettings = web.GetNavigationSettings();
                        if (navigationSettings != null)
                        {
                            if (navigationSettings.GlobalNavigation.ManagedNavigation)
                            {
                                scanResult.GlobalNavigationType = "Managed";
                            }
                            else
                            {
                                scanResult.GlobalNavigationType = "Structural";
                                scanResult.GlobalStructuralNavigationMaxCount     = navigationSettings.GlobalNavigation.MaxDynamicItems;
                                scanResult.GlobalStructuralNavigationShowPages    = navigationSettings.GlobalNavigation.ShowPages;
                                scanResult.GlobalStructuralNavigationShowSiblings = navigationSettings.GlobalNavigation.ShowSiblings;
                                scanResult.GlobalStructuralNavigationShowSubSites = navigationSettings.GlobalNavigation.ShowSubsites;
                            }

                            if (navigationSettings.CurrentNavigation.ManagedNavigation)
                            {
                                scanResult.CurrentNavigationType = "Managed";
                            }
                            else
                            {
                                scanResult.CurrentNavigationType = "Structural";
                                scanResult.CurrentStructuralNavigationMaxCount     = navigationSettings.CurrentNavigation.MaxDynamicItems;
                                scanResult.CurrentStructuralNavigationShowPages    = navigationSettings.CurrentNavigation.ShowPages;
                                scanResult.CurrentStructuralNavigationShowSiblings = navigationSettings.CurrentNavigation.ShowSiblings;
                                scanResult.CurrentStructuralNavigationShowSubSites = navigationSettings.CurrentNavigation.ShowSubsites;
                            }

                            if (navigationSettings.GlobalNavigation.ManagedNavigation || navigationSettings.CurrentNavigation.ManagedNavigation)
                            {
                                scanResult.ManagedNavigationAddNewPages        = navigationSettings.AddNewPagesToNavigation;
                                scanResult.ManagedNavigationCreateFriendlyUrls = navigationSettings.CreateFriendlyUrlsForNewPages;

                                // get information about the managed nav term set configuration
                                var managedNavXml = web.GetPropertyBagValueString(WebNavigationSettings, "");

                                if (!string.IsNullOrEmpty(managedNavXml))
                                {
                                    var managedNavSettings          = XElement.Parse(managedNavXml);
                                    IEnumerable <XElement> navNodes = managedNavSettings.XPathSelectElements("./SiteMapProviderSettings/TaxonomySiteMapProviderSettings");
                                    foreach (var node in navNodes)
                                    {
                                        if (node.Attribute("Name").Value.Equals("CurrentNavigationTaxonomyProvider", StringComparison.InvariantCulture))
                                        {
                                            if (node.Attribute("TermSetId") != null)
                                            {
                                                scanResult.CurrentManagedNavigationTermSetId = node.Attribute("TermSetId").Value;
                                            }
                                            else if (node.Attribute("UseParentSiteMap") != null)
                                            {
                                                scanResult.CurrentManagedNavigationTermSetId = "Inherit from parent";
                                            }
                                        }
                                        else if (node.Attribute("Name").Value.Equals("GlobalNavigationTaxonomyProvider", StringComparison.InvariantCulture))
                                        {
                                            if (node.Attribute("TermSetId") != null)
                                            {
                                                scanResult.GlobalManagedNavigationTermSetId = node.Attribute("TermSetId").Value;
                                            }
                                            else if (node.Attribute("UseParentSiteMap") != null)
                                            {
                                                scanResult.GlobalManagedNavigationTermSetId = "Inherit from parent";
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        // Pages library
                        var pagesLibrary = web.GetListsToScan().Where(p => p.BaseTemplate == 850).FirstOrDefault();
                        if (pagesLibrary != null)
                        {
                            pagesLibrary.EnsureProperties(p => p.EnableModeration, p => p.EnableVersioning, p => p.EnableMinorVersions, p => p.EventReceivers, p => p.Fields, p => p.DefaultContentApprovalWorkflowId);
                            scanResult.LibraryEnableModeration        = pagesLibrary.EnableModeration;
                            scanResult.LibraryEnableVersioning        = pagesLibrary.EnableVersioning;
                            scanResult.LibraryEnableMinorVersions     = pagesLibrary.EnableMinorVersions;
                            scanResult.LibraryItemScheduling          = pagesLibrary.ItemSchedulingEnabled();
                            scanResult.LibraryApprovalWorkflowDefined = pagesLibrary.DefaultContentApprovalWorkflowId != Guid.Empty;
                        }

                        // Variations
                        if (scanResult.Level == 0)
                        {
                            var variationLabels = cc.GetVariationLabels();

                            string labels      = "";
                            string sourceLabel = "";
                            foreach (var label in variationLabels)
                            {
                                labels = labels + $"{label.Title} ({label.Language}),";

                                if (label.IsSource)
                                {
                                    sourceLabel = label.Title;
                                }
                            }

                            scanResult.VariationLabels      = labels.TrimEnd(new char[] { ',' });;
                            scanResult.VariationSourceLabel = sourceLabel;
                        }

                        // Scan pages inside the pages library
                        if (pagesLibrary != null && Options.IncludePublishingWithPages(this.ScanJob.Mode))
                        {
                            CamlQuery query = new CamlQuery
                            {
                                ViewXml = CAMLQueryByExtension,
                            };

                            var pages = pagesLibrary.GetItems(query);

                            // Load additional page related information
                            IEnumerable <ListItem> enumerable = web.Context.LoadQuery(pages.IncludeWithDefaultProperties((ListItem item) => item.ContentType));
                            web.Context.ExecuteQueryRetry();

                            if (enumerable.FirstOrDefault() != null)
                            {
                                foreach (var page in enumerable)
                                {
                                    string pageUrl = null;
                                    try
                                    {
                                        if (page.FieldValues.ContainsKey(FileRefField) && !String.IsNullOrEmpty(page[FileRefField].ToString()))
                                        {
                                            pageUrl = page[FileRefField].ToString();
                                        }
                                        else
                                        {
                                            //skip page
                                            continue;
                                        }

                                        // Basic information about the page
                                        PublishingPageScanResult pageScanResult = new PublishingPageScanResult()
                                        {
                                            SiteColUrl      = this.SiteCollectionUrl,
                                            SiteURL         = this.SiteUrl,
                                            WebRelativeUrl  = scanResult.WebRelativeUrl,
                                            PageRelativeUrl = scanResult.WebRelativeUrl.Length > 0 ? pageUrl.Replace(scanResult.WebRelativeUrl, "") : pageUrl,
                                        };

                                        // Page name
                                        if (page.FieldValues.ContainsKey(FileLeafRefField) && !String.IsNullOrEmpty(page[FileLeafRefField].ToString()))
                                        {
                                            pageScanResult.PageName = page[FileLeafRefField].ToString();
                                        }

                                        // Get page change information
                                        pageScanResult.ModifiedAt = page.LastModifiedDateTime();
                                        if (!this.ScanJob.SkipUserInformation)
                                        {
                                            pageScanResult.ModifiedBy = page.LastModifiedBy();
                                        }

                                        // Page layout
                                        pageScanResult.PageLayout     = page.PageLayout();
                                        pageScanResult.PageLayoutFile = page.PageLayoutFile().Replace(pageScanResult.SiteColUrl, "").Replace("/_catalogs/masterpage/", "");

                                        // Customization status
                                        if (this.MasterPageGalleryCustomization == null)
                                        {
                                            this.MasterPageGalleryCustomization = new Dictionary <string, CustomizedPageStatus>();
                                        }

                                        // Load the file to check the customization status, only do this if the file was not loaded before for this site collection
                                        Uri uri = new Uri(page.PageLayoutFile());
                                        var url = page.PageLayoutFile().Replace($"{uri.Scheme}://{uri.DnsSafeHost}".ToLower(), "");
                                        if (!this.MasterPageGalleryCustomization.ContainsKey(url))
                                        {
                                            try
                                            {
                                                var publishingPageLayout = cc.Site.RootWeb.GetFileByServerRelativeUrl(url);
                                                cc.Load(publishingPageLayout);
                                                cc.ExecuteQueryRetry();

                                                this.MasterPageGalleryCustomization.Add(url, publishingPageLayout.CustomizedPageStatus);
                                            }
                                            catch (Exception ex)
                                            {
                                                // eat potential exceptions
                                            }
                                        }

                                        // store the page layout customization status
                                        if (this.MasterPageGalleryCustomization.TryGetValue(url, out CustomizedPageStatus pageStatus))
                                        {
                                            if (pageStatus == CustomizedPageStatus.Uncustomized)
                                            {
                                                pageScanResult.PageLayoutWasCustomized = false;
                                            }
                                            else
                                            {
                                                pageScanResult.PageLayoutWasCustomized = true;
                                            }
                                        }
                                        else
                                        {
                                            // If the file was not loaded for some reason then assume it was customized
                                            pageScanResult.PageLayoutWasCustomized = true;
                                        }

                                        // Page audiences
                                        var audiences = page.Audiences();
                                        if (audiences != null)
                                        {
                                            pageScanResult.GlobalAudiences          = audiences.GlobalAudiences;
                                            pageScanResult.SecurityGroupAudiences   = audiences.SecurityGroups;
                                            pageScanResult.SharePointGroupAudiences = audiences.SharePointGroups;
                                        }

                                        // Contenttype
                                        pageScanResult.ContentType   = page.ContentType.Name;
                                        pageScanResult.ContentTypeId = page.ContentType.Id.StringValue;

                                        // Get page web parts
                                        var pageAnalysis = page.WebParts(this.ScanJob.PageTransformation);
                                        if (pageAnalysis != null)
                                        {
                                            pageScanResult.WebParts = pageAnalysis.Item2;
                                        }

                                        // Persist publishing page scan results
                                        if (!this.ScanJob.PublishingPageScanResults.TryAdd(pageUrl, pageScanResult))
                                        {
                                            ScanError error = new ScanError()
                                            {
                                                Error      = $"Could not add publishing page scan result for {pageScanResult.PageRelativeUrl}",
                                                SiteColUrl = this.SiteCollectionUrl,
                                                SiteURL    = this.SiteUrl,
                                                Field1     = "PublishingAnalyzer",
                                                Field2     = pageScanResult.PageRelativeUrl,
                                            };
                                            this.ScanJob.ScanErrors.Push(error);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        ScanError error = new ScanError()
                                        {
                                            Error      = ex.Message,
                                            SiteColUrl = this.SiteCollectionUrl,
                                            SiteURL    = this.SiteUrl,
                                            Field1     = "MainPublishingPageAnalyzerLoop",
                                            Field2     = ex.StackTrace,
                                            Field3     = pageUrl
                                        };
                                        this.ScanJob.ScanErrors.Push(error);
                                        Console.WriteLine("Error for page {1}: {0}", ex.Message, pageUrl);
                                    }
                                }
                            }
                        }

                        // Persist publishing scan results
                        if (!this.ScanJob.PublishingWebScanResults.TryAdd(this.SiteUrl, scanResult))
                        {
                            ScanError error = new ScanError()
                            {
                                Error      = $"Could not add publishing scan result for {this.SiteUrl}",
                                SiteColUrl = this.SiteCollectionUrl,
                                SiteURL    = this.SiteUrl,
                                Field1     = "PublishingAnalyzer",
                            };
                            this.ScanJob.ScanErrors.Push(error);
                        }
                    }
                    catch (Exception ex)
                    {
                        ScanError error = new ScanError()
                        {
                            Error      = ex.Message,
                            SiteColUrl = this.SiteCollectionUrl,
                            SiteURL    = this.SiteUrl,
                            Field1     = "MainPublishingAnalyzerLoop",
                            Field2     = ex.StackTrace,
                        };
                        this.ScanJob.ScanErrors.Push(error);
                        Console.WriteLine("Error for web {1}: {0}", ex.Message, this.SiteUrl);
                    }
                }
            }
            finally
            {
                this.StopTime = DateTime.Now;
            }

            // return the duration of this scan
            return(new TimeSpan((this.StopTime.Subtract(this.StartTime).Ticks)));
        }
Example #2
0
        /// <summary>
        /// Override of the scanner execute method, needed to output our results
        /// </summary>
        /// <returns>Time when scanning was started</returns>
        public override DateTime Execute()
        {
            // Triggers the run of the scanning...will result in ModernizationScanJob_TimerJobRun being called per site collection
            var start = base.Execute();

            // Handle the export of the job specific scanning data
            string outputfile = string.Format("{0}\\ModernizationSiteScanResults.csv", this.OutputFolder);

            string[] outputHeaders = new string[] { "SiteCollectionUrl", "SiteUrl",
                                                    "ReadyForGroupify", "GroupifyBlockers", "GroupifyWarnings", "GroupMode", "PermissionWarnings",
                                                    "ModernHomePage", "ModernUIWarnings",
                                                    "WebTemplate", "Office365GroupId", "MasterPage", "AlternateCSS", "UserCustomActions",
                                                    "SubSites", "SubSitesWithBrokenPermissionInheritance", "ModernPageWebFeatureDisabled", "ModernPageFeatureWasEnabledBySPO",
                                                    "ModernListSiteBlockingFeatureEnabled", "ModernListWebBlockingFeatureEnabled", "SitePublishingFeatureEnabled", "WebPublishingFeatureEnabled",
                                                    "ViewsRecent", "ViewsRecentUniqueUsers", "ViewsLifeTime", "ViewsLifeTimeUniqueUsers",
                                                    "Everyone(ExceptExternalUsers)Claim", "UsesADGroups", "ExternalSharing",
                                                    "Admins", "AdminContainsEveryone(ExceptExternalUsers)Claim", "AdminContainsADGroups",
                                                    "Owners", "OwnersContainsEveryone(ExceptExternalUsers)Claim", "OwnersContainsADGroups",
                                                    "Members", "MembersContainsEveryone(ExceptExternalUsers)Claim", "MembersContainsADGroups",
                                                    "Visitors", "VisitorsContainsEveryone(ExceptExternalUsers)Claim", "VisitorsContainsADGroups" };
            Console.WriteLine("Outputting scan results to {0}", outputfile);
            using (StreamWriter outfile = new StreamWriter(outputfile))
            {
                outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, outputHeaders)));
                foreach (var item in this.SiteScanResults)
                {
                    var groupifyBlockers = item.Value.GroupifyBlockers();
                    var groupifyWarnings = item.Value.GroupifyWarnings(this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim);
                    var modernWarnings   = item.Value.ModernWarnings();
                    var groupSecurity    = item.Value.PermissionModel(this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim);

                    outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.SiteURL),
                                                                       (groupifyBlockers.Count > 0 ? "FALSE" : "TRUE"), ToCsv(SiteScanResult.FormatList(groupifyBlockers)), ToCsv(SiteScanResult.FormatList(groupifyWarnings)), ToCsv(groupSecurity.Item1), ToCsv(SiteScanResult.FormatList(groupSecurity.Item2)),
                                                                       item.Value.ModernHomePage, ToCsv(SiteScanResult.FormatList(modernWarnings)),
                                                                       ToCsv(item.Value.WebTemplate), ToCsv(item.Value.Office365GroupId != Guid.Empty ? item.Value.Office365GroupId.ToString() : ""), item.Value.MasterPage, item.Value.AlternateCSS, ((item.Value.SiteUserCustomActions != null && item.Value.SiteUserCustomActions.Count > 0) || (item.Value.WebUserCustomActions != null && item.Value.WebUserCustomActions.Count > 0)),
                                                                       item.Value.SubSites, item.Value.SubSitesWithBrokenPermissionInheritance, item.Value.ModernPageWebFeatureDisabled, item.Value.ModernPageFeatureWasEnabledBySPO,
                                                                       item.Value.ModernListSiteBlockingFeatureEnabled, item.Value.ModernListWebBlockingFeatureEnabled, item.Value.SitePublishingFeatureEnabled, item.Value.WebPublishingFeatureEnabled,
                                                                       (SkipUsageInformation ? 0: item.Value.ViewsRecent), (SkipUsageInformation ? 0 : item.Value.ViewsRecentUniqueUsers), (SkipUsageInformation ? 0 : item.Value.ViewsLifeTime), (SkipUsageInformation ? 0 : item.Value.ViewsLifeTimeUniqueUsers),
                                                                       item.Value.EveryoneClaimsGranted, item.Value.ContainsADGroup(), ToCsv(item.Value.SharingCapabilities),
                                                                       ToCsv(SiteScanResult.FormatUserList(item.Value.Admins, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim)), item.Value.HasClaim(item.Value.Admins, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim), item.Value.ContainsADGroup(item.Value.Admins),
                                                                       ToCsv(SiteScanResult.FormatUserList(item.Value.Owners, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim)), item.Value.HasClaim(item.Value.Owners, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim), item.Value.ContainsADGroup(item.Value.Owners),
                                                                       ToCsv(SiteScanResult.FormatUserList(item.Value.Members, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim)), item.Value.HasClaim(item.Value.Members, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim), item.Value.ContainsADGroup(item.Value.Members),
                                                                       ToCsv(SiteScanResult.FormatUserList(item.Value.Visitors, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim)), item.Value.HasClaim(item.Value.Visitors, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim), item.Value.ContainsADGroup(item.Value.Visitors)
                                                                       )));
                }
            }

            outputfile    = string.Format("{0}\\ModernizationWebScanResults.csv", this.OutputFolder);
            outputHeaders = new string[] { "SiteCollectionUrl", "SiteUrl",
                                           "WebTemplate", "BrokenPermissionInheritance", "ModernPageWebFeatureDisabled", "ModernPageFeatureWasEnabledBySPO", "WebPublishingFeatureEnabled",
                                           "MasterPage", "CustomMasterPage", "AlternateCSS", "UserCustomActions",
                                           "Everyone(ExceptExternalUsers)Claim",
                                           "UniqueOwners",
                                           "UniqueMembers",
                                           "UniqueVisitors" };
            Console.WriteLine("Outputting scan results to {0}", outputfile);
            using (StreamWriter outfile = new StreamWriter(outputfile))
            {
                outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, outputHeaders)));
                foreach (var item in this.WebScanResults)
                {
                    outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.SiteURL),
                                                                       ToCsv(item.Value.WebTemplate), item.Value.BrokenPermissionInheritance, item.Value.ModernPageWebFeatureDisabled, item.Value.ModernPageFeatureWasEnabledBySPO, item.Value.WebPublishingFeatureEnabled,
                                                                       ToCsv(item.Value.MasterPage), ToCsv(item.Value.CustomMasterPage), ToCsv(item.Value.AlternateCSS), (item.Value.WebUserCustomActions.Count > 0),
                                                                       item.Value.EveryoneClaimsGranted,
                                                                       ToCsv(SiteScanResult.FormatUserList(item.Value.Owners, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim)),
                                                                       ToCsv(SiteScanResult.FormatUserList(item.Value.Members, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim)),
                                                                       ToCsv(SiteScanResult.FormatUserList(item.Value.Visitors, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim))
                                                                       )));
                }
            }

            outputfile    = string.Format("{0}\\ModernizationUserCustomActionScanResults.csv", this.OutputFolder);
            outputHeaders = new string[] { "SiteCollectionUrl", "SiteUrl",
                                           "Title", "Name", "Location", "RegistrationType", "RegistrationId", "Reason", "CommandAction", "ScriptBlock", "ScriptSrc" };
            Console.WriteLine("Outputting scan results to {0}", outputfile);
            using (StreamWriter outfile = new StreamWriter(outputfile))
            {
                outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, outputHeaders)));
                foreach (var item in this.SiteScanResults)
                {
                    if (item.Value.SiteUserCustomActions == null || item.Value.SiteUserCustomActions.Count == 0)
                    {
                        continue;
                    }

                    foreach (var uca in item.Value.SiteUserCustomActions)
                    {
                        outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.SiteURL),
                                                                           ToCsv(uca.Title), ToCsv(uca.Name), ToCsv(uca.Location), uca.RegistrationType, ToCsv(uca.RegistrationId), ToCsv(uca.Problem), ToCsv(uca.CommandAction), ToCsv(uca.ScriptBlock), ToCsv(uca.ScriptSrc)
                                                                           )));
                    }
                }
                foreach (var item in this.WebScanResults)
                {
                    if (item.Value.WebUserCustomActions == null || item.Value.WebUserCustomActions.Count == 0)
                    {
                        continue;
                    }

                    foreach (var uca in item.Value.WebUserCustomActions)
                    {
                        outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.SiteURL),
                                                                           ToCsv(uca.Title), ToCsv(uca.Name), ToCsv(uca.Location), uca.RegistrationType, ToCsv(uca.RegistrationId), ToCsv(uca.Problem), ToCsv(uca.CommandAction), ToCsv(uca.ScriptBlock), ToCsv(uca.ScriptSrc)
                                                                           )));
                    }
                }
            }

            if (Options.IncludeLists(this.Mode))
            {
                outputfile    = string.Format("{0}\\ModernizationListScanResults.csv", this.OutputFolder);
                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" };

                Console.WriteLine("Outputting scan results to {0}", outputfile);
                using (StreamWriter outfile = new StreamWriter(outputfile))
                {
                    outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, outputHeaders)));
                    foreach (var list in this.ListScanResults)
                    {
                        outfile.Write(string.Format("{0}\r\n", string.Join(this.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)
                                                                           )));
                    }
                }
            }

            if (Options.IncludePage(this.Mode))
            {
                outputfile    = string.Format("{0}\\PageScanResults.csv", this.OutputFolder);
                outputHeaders = new string[] { "SiteCollectionUrl", "SiteUrl", "PageUrl", "Library", "HomePage",
                                               "Type", "Layout", "Mapping %", "Unmapped web parts", "ModifiedBy", "ModifiedAt",
                                               "ViewsRecent", "ViewsRecentUniqueUsers", "ViewsLifeTime", "ViewsLifeTimeUniqueUsers" };
                Console.WriteLine("Outputting scan results to {0}", outputfile);

                string header1 = string.Join(this.Separator, outputHeaders);
                string header2 = "";
                for (int i = 1; i <= 30; i++)
                {
                    if (ExportWebPartProperties)
                    {
                        header2 = header2 + $"{this.Separator}WPType{i}{this.Separator}WPTitle{i}{this.Separator}WPData{i}";
                    }
                    else
                    {
                        header2 = header2 + $"{this.Separator}WPType{i}{this.Separator}WPTitle{i}";
                    }
                }

                List <string> UniqueWebParts = new List <string>();
                using (StreamWriter outfile = new StreamWriter(outputfile))
                {
                    outfile.Write(string.Format("{0}\r\n", header1 + header2));
                    foreach (var item in this.PageScanResults)
                    {
                        var part1 = string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.SiteURL), ToCsv(item.Value.PageUrl), ToCsv(item.Value.Library), item.Value.HomePage,
                                                ToCsv(item.Value.PageType), ToCsv(item.Value.Layout), "{MappingPercentage}", "{UnmappedWebParts}", ToCsv(item.Value.ModifiedBy), item.Value.ModifiedAt,
                                                (SkipUsageInformation ? 0 : item.Value.ViewsRecent), (SkipUsageInformation ? 0 : item.Value.ViewsRecentUniqueUsers), (SkipUsageInformation ? 0 : item.Value.ViewsLifeTime), (SkipUsageInformation ? 0 : item.Value.ViewsLifeTimeUniqueUsers));

                        string part2 = "";
                        if (item.Value.WebParts != null)
                        {
                            int           webPartsOnPage       = item.Value.WebParts.Count();
                            int           webPartsOnPageMapped = 0;
                            List <string> nonMappedWebParts    = new List <string>();
                            foreach (var webPart in item.Value.WebParts.OrderBy(p => p.Row).ThenBy(p => p.Column).ThenBy(p => p.Order))
                            {
                                var found = this.PageTransformation.WebParts.Where(p => p.Type.Equals(webPart.Type, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                                if (found != null && found.Mappings != null)
                                {
                                    webPartsOnPageMapped++;
                                }
                                else
                                {
                                    var t = webPart.Type.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)[0];
                                    if (!nonMappedWebParts.Contains(t))
                                    {
                                        nonMappedWebParts.Add(t);
                                    }
                                }

                                if (ExportWebPartProperties)
                                {
                                    part2 = part2 + $"{this.Separator}{ToCsv(webPart.TypeShort())}{this.Separator}{ToCsv(webPart.Title)}{this.Separator}{ToCsv(webPart.Json())}";
                                }
                                else
                                {
                                    part2 = part2 + $"{this.Separator}{ToCsv(webPart.TypeShort())}{this.Separator}{ToCsv(webPart.Title)}";
                                }

                                if (!UniqueWebParts.Contains(webPart.Type))
                                {
                                    UniqueWebParts.Add(webPart.Type);
                                }
                            }
                            part1 = part1.Replace("{MappingPercentage}", webPartsOnPage == 0 ? "100" : String.Format("{0:0}", (((double)webPartsOnPageMapped / (double)webPartsOnPage) * 100))).Replace("{UnmappedWebParts}", SiteScanResult.FormatList(nonMappedWebParts));
                        }
                        else
                        {
                            part1 = part1.Replace("{MappingPercentage}", "").Replace("{UnmappedWebParts}", "");
                        }

                        outfile.Write(string.Format("{0}\r\n", part1 + (!string.IsNullOrEmpty(part2) ? part2 : "")));
                    }
                }

                outputfile = string.Format("{0}\\UniqueWebParts.csv", this.OutputFolder);
                Console.WriteLine("Outputting scan results to {0}", outputfile);
                using (StreamWriter outfile = new StreamWriter(outputfile))
                {
                    outfile.Write(string.Format("{0}\r\n", $"Type{this.Separator}InMappingFile"));
                    foreach (var type in UniqueWebParts)
                    {
                        var found = this.PageTransformation.WebParts.Where(p => p.Type.Equals(type, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                        outfile.Write(string.Format("{0}\r\n", $"{ToCsv(type)}{this.Separator}{found != null}"));
                    }
                }
            }

            if (Options.IncludePublishing(this.Mode))
            {
                // "Calculate" publishing site results based upon the web/page level data we retrieved
                this.PublishingSiteScanResults = PublishingAnalyzer.GeneratePublishingSiteResults(this.Mode, this.PublishingWebScanResults, this.PublishingPageScanResults);

                // Export the site publishing data
                outputfile    = string.Format("{0}\\ModernizationPublishingSiteScanResults.csv", this.OutputFolder);
                outputHeaders = new string[] { "SiteCollectionUrl", "NumberOfWebs", "NumberOfPages",
                                               "UsedSiteMasterPages", "UsedSystemMasterPages",
                                               "UsedPageLayouts", "LastPageUpdateDate" };
                Console.WriteLine("Outputting scan results to {0}", outputfile);
                using (StreamWriter outfile = new StreamWriter(outputfile))
                {
                    outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, outputHeaders)));
                    if (PublishingSiteScanResults != null)
                    {
                        foreach (var item in this.PublishingSiteScanResults)
                        {
                            outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), item.Value.NumberOfWebs, item.Value.NumberOfPages,
                                                                               ToCsv(PublishingPageScanResult.FormatList(item.Value.UsedSiteMasterPages)), ToCsv(PublishingPageScanResult.FormatList(item.Value.UsedSystemMasterPages)),
                                                                               ToCsv(PublishingPageScanResult.FormatList(item.Value.UsedPageLayouts)), item.Value.LastPageUpdateDate.HasValue ? item.Value.LastPageUpdateDate.ToString() : ""
                                                                               )));
                        }
                    }
                }

                // Export the web publishing data
                outputfile    = string.Format("{0}\\ModernizationPublishingWebScanResults.csv", this.OutputFolder);
                outputHeaders = new string[] { "SiteCollectionUrl", "SiteUrl", "WebRelativeUrl",
                                               "WebTemplate", "Level", "PageCount", "Language", "VariationLabels", "VariationSourceLabel",
                                               "SiteMasterPage", "SystemMasterPage", "AlternateCSS",
                                               "AllowedPageLayouts", "PageLayoutsConfiguration", "DefaultPageLayout",
                                               "GlobalNavigationType", "GlobalStructuralNavigationShowSubSites", "GlobalStructuralNavigationShowPages", "GlobalStructuralNavigationShowSiblings", "GlobalStructuralNavigationMaxCount", "GlobalManagedNavigationTermSetId",
                                               "CurrentNavigationType", "CurrentStructuralNavigationShowSubSites", "CurrentStructuralNavigationShowPages", "CurrentStructuralNavigationShowSiblings", "CurrentStructuralNavigationMaxCount", "CurrentManagedNavigationTermSetId",
                                               "ManagedNavigationAddNewPages", "ManagedNavigationCreateFriendlyUrls",
                                               "LibraryItemScheduling", "LibraryEnableModeration", "LibraryEnableVersioning", "LibraryEnableMinorVersions", "LibraryApprovalWorkflowDefined",
                                               "BrokenPermissionInheritance",
                                               "Admins",
                                               "Owners" };
                Console.WriteLine("Outputting scan results to {0}", outputfile);
                using (StreamWriter outfile = new StreamWriter(outputfile))
                {
                    outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, outputHeaders)));
                    foreach (var item in this.PublishingWebScanResults)
                    {
                        outfile.Write(string.Format("{0}\r\n", string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.SiteURL), ToCsv(item.Value.WebRelativeUrl),
                                                                           ToCsv(item.Value.WebTemplate), item.Value.Level.ToString(), item.Value.PageCount.ToString(), item.Value.Language.ToString(), ToCsv(item.Value.VariationLabels), ToCsv(item.Value.VariationSourceLabel),
                                                                           ToCsv(item.Value.SiteMasterPage), ToCsv(item.Value.SystemMasterPage), ToCsv(item.Value.AlternateCSS),
                                                                           ToCsv(item.Value.AllowedPageLayouts), ToCsv(item.Value.PageLayoutsConfiguration), ToCsv(item.Value.DefaultPageLayout),
                                                                           ToCsv(item.Value.GlobalNavigationType), item.Value.GlobalStructuralNavigationShowSubSites.HasValue ? item.Value.GlobalStructuralNavigationShowSubSites.Value.ToString() : "", item.Value.GlobalStructuralNavigationShowPages.HasValue ? item.Value.GlobalStructuralNavigationShowPages.Value.ToString() : "", item.Value.GlobalStructuralNavigationShowSiblings.HasValue ? item.Value.GlobalStructuralNavigationShowSiblings.Value.ToString() : "", item.Value.GlobalStructuralNavigationMaxCount.HasValue ? item.Value.GlobalStructuralNavigationMaxCount.Value.ToString() : "", ToCsv(item.Value.GlobalManagedNavigationTermSetId),
                                                                           ToCsv(item.Value.CurrentNavigationType), item.Value.CurrentStructuralNavigationShowSubSites.HasValue ? item.Value.CurrentStructuralNavigationShowSubSites.Value.ToString() : "", item.Value.CurrentStructuralNavigationShowPages.HasValue ? item.Value.CurrentStructuralNavigationShowPages.Value.ToString() : "", item.Value.CurrentStructuralNavigationShowSiblings.HasValue ? item.Value.CurrentStructuralNavigationShowSiblings.Value.ToString() : "", item.Value.CurrentStructuralNavigationMaxCount.HasValue ? item.Value.CurrentStructuralNavigationMaxCount.Value.ToString() : "", ToCsv(item.Value.CurrentManagedNavigationTermSetId),
                                                                           item.Value.ManagedNavigationAddNewPages.HasValue ? item.Value.ManagedNavigationAddNewPages.ToString() : "", item.Value.ManagedNavigationCreateFriendlyUrls.HasValue ? item.Value.ManagedNavigationCreateFriendlyUrls.ToString() : "",
                                                                           item.Value.LibraryItemScheduling.ToString(), item.Value.LibraryEnableModeration.ToString(), item.Value.LibraryEnableVersioning.ToString(), item.Value.LibraryEnableMinorVersions.ToString(), item.Value.LibraryApprovalWorkflowDefined.ToString(),
                                                                           item.Value.BrokenPermissionInheritance.ToString(),
                                                                           ToCsv(SiteScanResult.FormatUserList(item.Value.Admins, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim)),
                                                                           ToCsv(SiteScanResult.FormatUserList(item.Value.Owners, this.EveryoneClaim, this.EveryoneExceptExternalUsersClaim))
                                                                           )));
                    }
                }

                if (Options.IncludePublishingWithPages(this.Mode))
                {
                    // Export the page publishing data
                    outputfile    = string.Format("{0}\\ModernizationPublishingPageScanResults.csv", this.OutputFolder);
                    outputHeaders = new string[] { "SiteCollectionUrl", "SiteUrl", "WebRelativeUrl", "PageRelativeUrl", "PageName",
                                                   "ContentType", "ContentTypeId", "PageLayout", "PageLayoutFile",
                                                   "GlobalAudiences", "SecurityGroupAudiences", "SharePointGroupAudiences",
                                                   "ModifiedAt", "ModifiedBy", "Mapping %" };

                    string header1 = string.Join(this.Separator, outputHeaders);
                    string header2 = "";
                    for (int i = 1; i <= 20; i++)
                    {
                        if (ExportWebPartProperties)
                        {
                            header2 = header2 + $"{this.Separator}WPType{i}{this.Separator}WPTitle{i}{this.Separator}WPData{i}";
                        }
                        else
                        {
                            header2 = header2 + $"{this.Separator}WPType{i}{this.Separator}WPTitle{i}";
                        }
                    }

                    Console.WriteLine("Outputting scan results to {0}", outputfile);
                    using (StreamWriter outfile = new StreamWriter(outputfile))
                    {
                        outfile.Write(string.Format("{0}\r\n", header1 + header2));
                        foreach (var item in this.PublishingPageScanResults)
                        {
                            var part1 = string.Join(this.Separator, ToCsv(item.Value.SiteColUrl), ToCsv(item.Value.SiteURL), ToCsv(item.Value.WebRelativeUrl), ToCsv(item.Value.PageRelativeUrl), ToCsv(item.Value.PageName),
                                                    ToCsv(item.Value.ContentType), ToCsv(item.Value.ContentTypeId), ToCsv(item.Value.PageLayout), ToCsv(item.Value.PageLayoutFile),
                                                    ToCsv(PublishingPageScanResult.FormatList(item.Value.GlobalAudiences)), ToCsv(PublishingPageScanResult.FormatList(item.Value.SecurityGroupAudiences, "|")), ToCsv(PublishingPageScanResult.FormatList(item.Value.SharePointGroupAudiences)),
                                                    item.Value.ModifiedAt, ToCsv(item.Value.ModifiedBy), "{MappingPercentage}"
                                                    );

                            string part2 = "";
                            if (item.Value.WebParts != null)
                            {
                                int           webPartsOnPage       = item.Value.WebParts.Count();
                                int           webPartsOnPageMapped = 0;
                                List <string> nonMappedWebParts    = new List <string>();
                                foreach (var webPart in item.Value.WebParts.OrderBy(p => p.Row).ThenBy(p => p.Column).ThenBy(p => p.Order))
                                {
                                    var found = this.PageTransformation.WebParts.Where(p => p.Type.Equals(webPart.Type, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
                                    if (found != null && found.Mappings != null)
                                    {
                                        webPartsOnPageMapped++;
                                    }
                                    else
                                    {
                                        var t = webPart.Type.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)[0];
                                        if (!nonMappedWebParts.Contains(t))
                                        {
                                            nonMappedWebParts.Add(t);
                                        }
                                    }

                                    if (ExportWebPartProperties)
                                    {
                                        part2 = part2 + $"{this.Separator}{ToCsv(webPart.TypeShort())}{this.Separator}{ToCsv(webPart.Title)}{this.Separator}{ToCsv(webPart.Json())}";
                                    }
                                    else
                                    {
                                        part2 = part2 + $"{this.Separator}{ToCsv(webPart.TypeShort())}{this.Separator}{ToCsv(webPart.Title)}";
                                    }
                                }
                                part1 = part1.Replace("{MappingPercentage}", webPartsOnPage == 0 ? "100" : String.Format("{0:0}", (((double)webPartsOnPageMapped / (double)webPartsOnPage) * 100))).Replace("{UnmappedWebParts}", SiteScanResult.FormatList(nonMappedWebParts));
                            }
                            else
                            {
                                part1 = part1.Replace("{MappingPercentage}", "").Replace("{UnmappedWebParts}", "");
                            }

                            outfile.Write(string.Format("{0}\r\n", part1 + (!string.IsNullOrEmpty(part2) ? part2 : "")));
                        }
                    }
                }
            }

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

            return(start);
        }