/// <summary> /// Analyses a web for it's workflow usage /// </summary> /// <param name="cc">ClientContext instance used to retrieve workflow data</param> /// <returns>Duration of the workflow analysis</returns> public override TimeSpan Analyze(ClientContext cc) { try { // Workflow analysis does not work as the xoml / xaml files can't be read with Sites.Read.All permission if (!this.ScanJob.AppOnlyHasFullControl) { return(TimeSpan.Zero); } Web web = cc.Web; // Pre-load needed properties in a single call cc.Load(web, w => w.Id, w => w.ServerRelativeUrl, w => w.Url, w => w.WorkflowTemplates, w => w.WorkflowAssociations); cc.Load(web, p => p.ContentTypes.Include(ct => ct.WorkflowAssociations, ct => ct.Name, ct => ct.StringId)); cc.Load(web, p => p.Lists.Include(li => li.Id, li => li.Title, li => li.Hidden, li => li.DefaultViewUrl, li => li.BaseTemplate, li => li.RootFolder.ServerRelativeUrl, li => li.ItemCount, li => li.WorkflowAssociations)); cc.Load(cc.Site, p => p.RootWeb); cc.Load(cc.Site.RootWeb, p => p.Lists.Include(li => li.Id, li => li.Title, li => li.Hidden, li => li.DefaultViewUrl, li => li.BaseTemplate, li => li.RootFolder.ServerRelativeUrl, li => li.ItemCount, li => li.WorkflowAssociations)); cc.ExecuteQueryRetry(); var lists = web.Lists; // ******************************************* // Site, reusable and list level 2013 workflow // ******************************************* // Retrieve the 2013 site level workflow definitions (including unpublished ones) WorkflowDefinition[] siteDefinitions = null; // Retrieve the 2013 site level workflow subscriptions WorkflowSubscription[] siteSubscriptions = null; try { var servicesManager = new WorkflowServicesManager(web.Context, web); var deploymentService = servicesManager.GetWorkflowDeploymentService(); var subscriptionService = servicesManager.GetWorkflowSubscriptionService(); var definitions = deploymentService.EnumerateDefinitions(false); web.Context.Load(definitions); var subscriptions = subscriptionService.EnumerateSubscriptions(); web.Context.Load(subscriptions); web.Context.ExecuteQueryRetry(); siteDefinitions = definitions.ToArray(); siteSubscriptions = subscriptions.ToArray(); } catch (ServerException ex) { // If there is no workflow service present in the farm this method will throw an error. // Swallow the exception } // We've found SP2013 site scoped workflows if (siteDefinitions != null && siteDefinitions.Count() > 0) { foreach (var siteDefinition in siteDefinitions.Where(p => p.RestrictToType != null && (p.RestrictToType.Equals("site", StringComparison.InvariantCultureIgnoreCase) || p.RestrictToType.Equals("universal", StringComparison.InvariantCultureIgnoreCase)))) { // Check if this workflow is also in use var siteWorkflowSubscriptions = siteSubscriptions.Where(p => p.DefinitionId.Equals(siteDefinition.Id)); // Perform workflow analysis WorkflowActionAnalysis workFlowAnalysisResult = null; WorkflowTriggerAnalysis workFlowTriggerAnalysisResult = null; if (Options.IncludeWorkflowWithDetails(this.ScanJob.Mode)) { workFlowAnalysisResult = WorkflowManager.Instance.ParseWorkflowDefinition(siteDefinition.Xaml, WorkflowTypes.SP2013); workFlowTriggerAnalysisResult = WorkflowManager.Instance.ParseWorkflowTriggers(GetWorkflowPropertyBool(siteDefinition.Properties, "SPDConfig.StartOnCreate"), GetWorkflowPropertyBool(siteDefinition.Properties, "SPDConfig.StartOnChange"), GetWorkflowPropertyBool(siteDefinition.Properties, "SPDConfig.StartManually")); } if (siteWorkflowSubscriptions.Count() > 0) { foreach (var siteWorkflowSubscription in siteWorkflowSubscriptions) { WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "Site", RestrictToType = siteDefinition.RestrictToType, DefinitionName = siteDefinition.DisplayName, DefinitionDescription = siteDefinition.Description, SubscriptionName = siteWorkflowSubscription.Name, HasSubscriptions = true, Enabled = siteWorkflowSubscription.Enabled, DefinitionId = siteDefinition.Id, IsOOBWorkflow = false, SubscriptionId = siteWorkflowSubscription.Id, UsedActions = workFlowAnalysisResult?.WorkflowActions, ActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.ActionCount : 0, UsedTriggers = workFlowTriggerAnalysisResult?.WorkflowTriggers, UnsupportedActionsInFlow = workFlowAnalysisResult?.UnsupportedActions, UnsupportedActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.UnsupportedAccountCount : 0, LastDefinitionEdit = GetWorkflowPropertyDateTime(siteDefinition.Properties, "Definition.ModifiedDateUTC"), LastSubscriptionEdit = GetWorkflowPropertyDateTime(siteWorkflowSubscription.PropertyDefinitions, "SharePointWorkflowContext.Subscription.ModifiedDateUTC"), }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 site workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } else { WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "Site", RestrictToType = siteDefinition.RestrictToType, DefinitionName = siteDefinition.DisplayName, DefinitionDescription = siteDefinition.Description, SubscriptionName = "", HasSubscriptions = false, Enabled = false, DefinitionId = siteDefinition.Id, IsOOBWorkflow = false, SubscriptionId = Guid.Empty, UsedActions = workFlowAnalysisResult?.WorkflowActions, ActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.ActionCount : 0, UnsupportedActionsInFlow = workFlowAnalysisResult?.UnsupportedActions, UnsupportedActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.UnsupportedAccountCount : 0, UsedTriggers = workFlowTriggerAnalysisResult?.WorkflowTriggers, LastDefinitionEdit = GetWorkflowPropertyDateTime(siteDefinition.Properties, "Definition.ModifiedDateUTC"), }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 site workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } // We've found SP2013 list scoped workflows if (siteDefinitions != null && siteDefinitions.Count() > 0) { foreach (var listDefinition in siteDefinitions.Where(p => p.RestrictToType != null && (p.RestrictToType.Equals("list", StringComparison.InvariantCultureIgnoreCase) || p.RestrictToType.Equals("universal", StringComparison.InvariantCultureIgnoreCase)))) { // Check if this workflow is also in use var listWorkflowSubscriptions = siteSubscriptions.Where(p => p.DefinitionId.Equals(listDefinition.Id)); // Perform workflow analysis WorkflowActionAnalysis workFlowAnalysisResult = null; WorkflowTriggerAnalysis workFlowTriggerAnalysisResult = null; if (Options.IncludeWorkflowWithDetails(this.ScanJob.Mode)) { workFlowAnalysisResult = WorkflowManager.Instance.ParseWorkflowDefinition(listDefinition.Xaml, WorkflowTypes.SP2013); workFlowTriggerAnalysisResult = WorkflowManager.Instance.ParseWorkflowTriggers(GetWorkflowPropertyBool(listDefinition.Properties, "SPDConfig.StartOnCreate"), GetWorkflowPropertyBool(listDefinition.Properties, "SPDConfig.StartOnChange"), GetWorkflowPropertyBool(listDefinition.Properties, "SPDConfig.StartManually")); } if (listWorkflowSubscriptions.Count() > 0) { foreach (var listWorkflowSubscription in listWorkflowSubscriptions) { Guid associatedListId = Guid.Empty; string associatedListTitle = ""; string associatedListUrl = ""; if (Guid.TryParse(GetWorkflowProperty(listWorkflowSubscription, "Microsoft.SharePoint.ActivationProperties.ListId"), out Guid associatedListIdValue)) { associatedListId = associatedListIdValue; // Lookup this list and update title and url var listLookup = lists.Where(p => p.Id.Equals(associatedListId)).FirstOrDefault(); if (listLookup != null) { associatedListTitle = listLookup.Title; associatedListUrl = listLookup.RootFolder.ServerRelativeUrl; } } WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = associatedListTitle, ListUrl = associatedListUrl, ListId = associatedListId, ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "List", RestrictToType = listDefinition.RestrictToType, DefinitionName = listDefinition.DisplayName, DefinitionDescription = listDefinition.Description, SubscriptionName = listWorkflowSubscription.Name, HasSubscriptions = true, Enabled = listWorkflowSubscription.Enabled, DefinitionId = listDefinition.Id, IsOOBWorkflow = false, SubscriptionId = listWorkflowSubscription.Id, UsedActions = workFlowAnalysisResult?.WorkflowActions, ActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.ActionCount : 0, UsedTriggers = workFlowTriggerAnalysisResult?.WorkflowTriggers, UnsupportedActionsInFlow = workFlowAnalysisResult?.UnsupportedActions, UnsupportedActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.UnsupportedAccountCount : 0, LastDefinitionEdit = GetWorkflowPropertyDateTime(listDefinition.Properties, "Definition.ModifiedDateUTC"), LastSubscriptionEdit = GetWorkflowPropertyDateTime(listWorkflowSubscription.PropertyDefinitions, "SharePointWorkflowContext.Subscription.ModifiedDateUTC"), }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 list workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } else { WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ListId = Guid.Empty, ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "List", RestrictToType = listDefinition.RestrictToType, DefinitionName = listDefinition.DisplayName, DefinitionDescription = listDefinition.Description, SubscriptionName = "", HasSubscriptions = false, Enabled = false, DefinitionId = listDefinition.Id, IsOOBWorkflow = false, SubscriptionId = Guid.Empty, UsedActions = workFlowAnalysisResult?.WorkflowActions, ActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.ActionCount : 0, UsedTriggers = workFlowTriggerAnalysisResult?.WorkflowTriggers, UnsupportedActionsInFlow = workFlowAnalysisResult?.UnsupportedActions, UnsupportedActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.UnsupportedAccountCount : 0, LastDefinitionEdit = GetWorkflowPropertyDateTime(listDefinition.Properties, "Definition.ModifiedDateUTC"), }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 list workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } // *********************************************** // Site, list and content type level 2010 workflow // *********************************************** // Find all places where we have workflows associated (=subscribed) to SharePoint objects if (web.WorkflowAssociations.Count > 0) { foreach (var workflowAssociation in web.WorkflowAssociations) { this.sp2010WorkflowAssociations.Add(new SP2010WorkFlowAssociation() { Scope = "Site", WorkflowAssociation = workflowAssociation }); } } foreach (var list in lists.Where(p => p.WorkflowAssociations.Count > 0)) { foreach (var workflowAssociation in list.WorkflowAssociations) { this.sp2010WorkflowAssociations.Add(new SP2010WorkFlowAssociation() { Scope = "List", WorkflowAssociation = workflowAssociation, AssociatedList = list }); } } foreach (var ct in web.ContentTypes.Where(p => p.WorkflowAssociations.Count > 0)) { foreach (var workflowAssociation in ct.WorkflowAssociations) { this.sp2010WorkflowAssociations.Add(new SP2010WorkFlowAssociation() { Scope = "ContentType", WorkflowAssociation = workflowAssociation, AssociatedContentType = ct }); } } // Process 2010 worflows List <Guid> processedWorkflowAssociations = new List <Guid>(this.sp2010WorkflowAssociations.Count); if (web.WorkflowTemplates.Count > 0) { // Process the templates foreach (var workflowTemplate in web.WorkflowTemplates) { // do we have workflows associated for this template? var associatedWorkflows = this.sp2010WorkflowAssociations.Where(p => p.WorkflowAssociation.BaseId.Equals(workflowTemplate.Id)); if (associatedWorkflows.Count() > 0) { // Perform workflow analysis // If returning null than this workflow template was an OOB workflow one WorkflowActionAnalysis workFlowAnalysisResult = null; Tuple <string, DateTime> loadedWorkflow = null; if (Options.IncludeWorkflowWithDetails(this.ScanJob.Mode)) { loadedWorkflow = LoadWorkflowDefinition(cc, workflowTemplate); if (!string.IsNullOrEmpty(loadedWorkflow?.Item1)) { workFlowAnalysisResult = WorkflowManager.Instance.ParseWorkflowDefinition(loadedWorkflow.Item1, WorkflowTypes.SP2010); } } foreach (var associatedWorkflow in associatedWorkflows) { processedWorkflowAssociations.Add(associatedWorkflow.WorkflowAssociation.Id); // Skip previous versions of a workflow // TODO: non-english sites will use another string if (associatedWorkflow.WorkflowAssociation.Name.Contains("(Previous Version:")) { continue; } WorkflowTriggerAnalysis workFlowTriggerAnalysisResult = null; if (Options.IncludeWorkflowWithDetails(this.ScanJob.Mode)) { workFlowTriggerAnalysisResult = WorkflowManager.Instance.ParseWorkflowTriggers(associatedWorkflow.WorkflowAssociation.AutoStartCreate, associatedWorkflow.WorkflowAssociation.AutoStartChange, associatedWorkflow.WorkflowAssociation.AllowManual); } WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Title : "", ListUrl = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.RootFolder.ServerRelativeUrl : "", ListId = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Id : Guid.Empty, ContentTypeId = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.StringId : "", ContentTypeName = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.Name : "", Version = "2010", Scope = associatedWorkflow.Scope, RestrictToType = "N/A", DefinitionName = workflowTemplate.Name, DefinitionDescription = workflowTemplate.Description, SubscriptionName = associatedWorkflow.WorkflowAssociation.Name, HasSubscriptions = true, Enabled = associatedWorkflow.WorkflowAssociation.Enabled, DefinitionId = workflowTemplate.Id, IsOOBWorkflow = IsOOBWorkflow(workflowTemplate.Id.ToString()), SubscriptionId = associatedWorkflow.WorkflowAssociation.Id, UsedActions = workFlowAnalysisResult?.WorkflowActions, ActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.ActionCount : 0, UsedTriggers = workFlowTriggerAnalysisResult?.WorkflowTriggers, UnsupportedActionsInFlow = workFlowAnalysisResult?.UnsupportedActions, UnsupportedActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.UnsupportedAccountCount : 0, LastDefinitionEdit = loadedWorkflow != null ? loadedWorkflow.Item2 : associatedWorkflow.WorkflowAssociation.Modified, LastSubscriptionEdit = associatedWorkflow.WorkflowAssociation.Modified, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2010 {associatedWorkflow.Scope} type workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } else { // Only add non OOB workflow templates when there's no associated workflow - makes the dataset smaller if (!IsOOBWorkflow(workflowTemplate.Id.ToString())) { // Perform workflow analysis WorkflowActionAnalysis workFlowAnalysisResult = null; WorkflowTriggerAnalysis workFlowTriggerAnalysisResult = null; Tuple <string, DateTime> loadedWorkflow = null; if (Options.IncludeWorkflowWithDetails(this.ScanJob.Mode)) { loadedWorkflow = LoadWorkflowDefinition(cc, workflowTemplate); if (!string.IsNullOrEmpty(loadedWorkflow?.Item1)) { workFlowAnalysisResult = WorkflowManager.Instance.ParseWorkflowDefinition(loadedWorkflow.Item1, WorkflowTypes.SP2010); } workFlowTriggerAnalysisResult = WorkflowManager.Instance.ParseWorkflowTriggers(workflowTemplate.AutoStartCreate, workflowTemplate.AutoStartChange, workflowTemplate.AllowManual); } WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ListId = Guid.Empty, ContentTypeId = "", ContentTypeName = "", Version = "2010", Scope = "", RestrictToType = "N/A", DefinitionName = workflowTemplate.Name, DefinitionDescription = workflowTemplate.Description, SubscriptionName = "", HasSubscriptions = false, Enabled = false, DefinitionId = workflowTemplate.Id, IsOOBWorkflow = IsOOBWorkflow(workflowTemplate.Id.ToString()), SubscriptionId = Guid.Empty, UsedActions = workFlowAnalysisResult?.WorkflowActions, ActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.ActionCount : 0, UsedTriggers = workFlowTriggerAnalysisResult?.WorkflowTriggers, LastDefinitionEdit = loadedWorkflow != null ? loadedWorkflow.Item2 : DateTime.MinValue, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2010 type workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } } // Are there associated workflows for which we did not find a template (especially when the WF is created for a list) foreach (var associatedWorkflow in this.sp2010WorkflowAssociations) { if (!processedWorkflowAssociations.Contains(associatedWorkflow.WorkflowAssociation.Id)) { // Skip previous versions of a workflow // TODO: non-english sites will use another string if (associatedWorkflow.WorkflowAssociation.Name.Contains("(Previous Version:")) { continue; } // Perform workflow analysis WorkflowActionAnalysis workFlowAnalysisResult = null; WorkflowTriggerAnalysis workFlowTriggerAnalysisResult = null; Tuple <string, DateTime> loadedWorkflow = null; if (Options.IncludeWorkflowWithDetails(this.ScanJob.Mode)) { loadedWorkflow = LoadWorkflowDefinition(cc, associatedWorkflow.WorkflowAssociation); if (!string.IsNullOrEmpty(loadedWorkflow?.Item1)) { workFlowAnalysisResult = WorkflowManager.Instance.ParseWorkflowDefinition(loadedWorkflow.Item1, WorkflowTypes.SP2010); } workFlowTriggerAnalysisResult = WorkflowManager.Instance.ParseWorkflowTriggers(associatedWorkflow.WorkflowAssociation.AutoStartCreate, associatedWorkflow.WorkflowAssociation.AutoStartChange, associatedWorkflow.WorkflowAssociation.AllowManual); } WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Title : "", ListUrl = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.RootFolder.ServerRelativeUrl : "", ListId = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Id : Guid.Empty, ContentTypeId = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.StringId : "", ContentTypeName = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.Name : "", Version = "2010", Scope = associatedWorkflow.Scope, RestrictToType = "N/A", DefinitionName = associatedWorkflow.WorkflowAssociation.Name, DefinitionDescription = "", SubscriptionName = associatedWorkflow.WorkflowAssociation.Name, HasSubscriptions = true, Enabled = associatedWorkflow.WorkflowAssociation.Enabled, DefinitionId = Guid.Empty, IsOOBWorkflow = false, SubscriptionId = associatedWorkflow.WorkflowAssociation.Id, UsedActions = workFlowAnalysisResult?.WorkflowActions, ActionCount = workFlowAnalysisResult != null ? workFlowAnalysisResult.ActionCount : 0, UsedTriggers = workFlowTriggerAnalysisResult?.WorkflowTriggers, LastSubscriptionEdit = associatedWorkflow.WorkflowAssociation.Modified, LastDefinitionEdit = loadedWorkflow != null ? loadedWorkflow.Item2 : associatedWorkflow.WorkflowAssociation.Modified, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2010 {associatedWorkflow.Scope} type workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } catch (Exception ex) { ScanError error = new ScanError() { Error = ex.Message, SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", Field2 = ex.StackTrace, }; // Send error to telemetry to make scanner better if (this.ScanJob.ScannerTelemetry != null) { this.ScanJob.ScannerTelemetry.LogScanError(ex, error); } this.ScanJob.ScanErrors.Push(error); } finally { this.StopTime = DateTime.Now; } // return the duration of this scan return(new TimeSpan((this.StopTime.Subtract(this.StartTime).Ticks))); }
/// <summary> /// Analyses a web for it's workflow usage /// </summary> /// <param name="cc">ClientContext instance used to retrieve workflow data</param> /// <returns>Duration of the workflow analysis</returns> public override TimeSpan Analyze(ClientContext cc) { try { Web web = cc.Web; // Pre-load needed properties in a single call cc.Load(web, w => w.Id, w => w.ServerRelativeUrl, w => w.Url, w => w.WorkflowTemplates, w => w.WorkflowAssociations); cc.Load(web, p => p.ContentTypes.Include(ct => ct.WorkflowAssociations, ct => ct.Name, ct => ct.StringId)); cc.Load(web, p => p.Lists.Include(li => li.Id, li => li.Title, li => li.Hidden, li => li.DefaultViewUrl, li => li.BaseTemplate, li => li.RootFolder, li => li.ItemCount, li => li.WorkflowAssociations)); cc.ExecuteQueryRetry(); var lists = web.Lists; // ******************************************* // Site, reusable and list level 2013 workflow // ******************************************* // Retrieve the 2013 site level workflow definitions (including unpublished ones) WorkflowDefinition[] siteDefinitions = null; // Retrieve the 2013 site level workflow subscriptions WorkflowSubscription[] siteSubscriptions = null; try { var servicesManager = new WorkflowServicesManager(web.Context, web); var deploymentService = servicesManager.GetWorkflowDeploymentService(); var subscriptionService = servicesManager.GetWorkflowSubscriptionService(); var definitions = deploymentService.EnumerateDefinitions(false); web.Context.Load(definitions); var subscriptions = subscriptionService.EnumerateSubscriptions(); web.Context.Load(subscriptions); web.Context.ExecuteQueryRetry(); siteDefinitions = definitions.ToArray(); siteSubscriptions = subscriptions.ToArray(); } catch (ServerException) { // If there is no workflow service present in the farm this method will throw an error. // Swallow the exception } // We've found SP2013 site scoped workflows if (siteDefinitions.Count() > 0) { foreach (var siteDefinition in siteDefinitions.Where(p => p.RestrictToType.Equals("site", StringComparison.InvariantCultureIgnoreCase) || p.RestrictToType.Equals("universal", StringComparison.InvariantCultureIgnoreCase))) { // Check if this workflow is also in use var siteWorkflowSubscriptions = siteSubscriptions.Where(p => p.DefinitionId.Equals(siteDefinition.Id)); if (siteWorkflowSubscriptions.Count() > 0) { foreach (var siteWorkflowSubscription in siteWorkflowSubscriptions) { WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "Site", RestrictToType = siteDefinition.RestrictToType, DefinitionName = siteDefinition.DisplayName, DefinitionDescription = siteDefinition.Description, SubscriptionName = siteWorkflowSubscription.Name, HasSubscriptions = true, Enabled = siteWorkflowSubscription.Enabled, DefinitionId = siteDefinition.Id, IsOOBWorkflow = false, SubscriptionId = siteWorkflowSubscription.Id, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 site workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } else { WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "Site", RestrictToType = siteDefinition.RestrictToType, DefinitionName = siteDefinition.DisplayName, DefinitionDescription = siteDefinition.Description, SubscriptionName = "", HasSubscriptions = false, Enabled = false, DefinitionId = siteDefinition.Id, IsOOBWorkflow = false, SubscriptionId = Guid.Empty, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 site workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } // We've found SP2013 list scoped workflows if (siteDefinitions.Count() > 0) { foreach (var listDefinition in siteDefinitions.Where(p => p.RestrictToType.Equals("list", StringComparison.InvariantCultureIgnoreCase) || p.RestrictToType.Equals("universal", StringComparison.InvariantCultureIgnoreCase))) { // Check if this workflow is also in use var listWorkflowSubscriptions = siteSubscriptions.Where(p => p.DefinitionId.Equals(listDefinition.Id)); if (listWorkflowSubscriptions.Count() > 0) { foreach (var listWorkflowSubscription in listWorkflowSubscriptions) { Guid associatedListId = Guid.Empty; string associatedListTitle = ""; string associatedListUrl = ""; if (Guid.TryParse(GetWorkflowProperty(listWorkflowSubscription, "Microsoft.SharePoint.ActivationProperties.ListId"), out Guid associatedListIdValue)) { associatedListId = associatedListIdValue; // Lookup this list and update title and url var listLookup = lists.Where(p => p.Id.Equals(associatedListId)).FirstOrDefault(); if (listLookup != null) { associatedListTitle = listLookup.Title; associatedListUrl = listLookup.RootFolder.ServerRelativeUrl; } } WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = associatedListTitle, ListUrl = associatedListUrl, ListId = associatedListId, ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "List", RestrictToType = listDefinition.RestrictToType, DefinitionName = listDefinition.DisplayName, DefinitionDescription = listDefinition.Description, SubscriptionName = listWorkflowSubscription.Name, HasSubscriptions = true, Enabled = listWorkflowSubscription.Enabled, DefinitionId = listDefinition.Id, IsOOBWorkflow = false, SubscriptionId = listWorkflowSubscription.Id, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 list workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } else { WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ListId = Guid.Empty, ContentTypeId = "", ContentTypeName = "", Version = "2013", Scope = "List", RestrictToType = listDefinition.RestrictToType, DefinitionName = listDefinition.DisplayName, DefinitionDescription = listDefinition.Description, SubscriptionName = "", HasSubscriptions = false, Enabled = false, DefinitionId = listDefinition.Id, IsOOBWorkflow = false, SubscriptionId = Guid.Empty, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2013 list workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } // *********************************************** // Site, list and content type level 2010 workflow // *********************************************** // Find all places where we have workflows associated (=subscribed) to SharePoint objects if (web.WorkflowAssociations.Count > 0) { foreach (var workflowAssociation in web.WorkflowAssociations) { this.sp2010WorkflowAssociations.Add(new SP2010WorkFlowAssociation() { Scope = "Site", WorkflowAssociation = workflowAssociation }); } } foreach (var list in lists.Where(p => p.WorkflowAssociations.Count > 0)) { foreach (var workflowAssociation in list.WorkflowAssociations) { this.sp2010WorkflowAssociations.Add(new SP2010WorkFlowAssociation() { Scope = "List", WorkflowAssociation = workflowAssociation, AssociatedList = list }); } } foreach (var ct in web.ContentTypes.Where(p => p.WorkflowAssociations.Count > 0)) { foreach (var workflowAssociation in ct.WorkflowAssociations) { this.sp2010WorkflowAssociations.Add(new SP2010WorkFlowAssociation() { Scope = "ContentType", WorkflowAssociation = workflowAssociation, AssociatedContentType = ct }); } } // Process 2010 worflows System.Collections.Generic.List <Guid> processedWorkflowAssociations = new System.Collections.Generic.List <Guid>(this.sp2010WorkflowAssociations.Count); if (web.WorkflowTemplates.Count > 0) { foreach (var workflowTemplate in web.WorkflowTemplates) { // do we have workflows associated for this template? var associatedWorkflows = this.sp2010WorkflowAssociations.Where(p => p.WorkflowAssociation.BaseId.Equals(workflowTemplate.Id)); if (associatedWorkflows.Count() > 0) { foreach (var associatedWorkflow in associatedWorkflows) { processedWorkflowAssociations.Add(associatedWorkflow.WorkflowAssociation.Id); // Skip previous versions of a workflow // todo: non-english sites will use another string if (associatedWorkflow.WorkflowAssociation.Name.Contains("(Previous Version:")) { continue; } WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Title : "", ListUrl = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.RootFolder.ServerRelativeUrl : "", ListId = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Id : Guid.Empty, ContentTypeId = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.StringId : "", ContentTypeName = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.Name : "", Version = "2010", Scope = associatedWorkflow.Scope, RestrictToType = "N/A", DefinitionName = workflowTemplate.Name, DefinitionDescription = workflowTemplate.Description, SubscriptionName = associatedWorkflow.WorkflowAssociation.Name, HasSubscriptions = true, Enabled = associatedWorkflow.WorkflowAssociation.Enabled, DefinitionId = workflowTemplate.Id, IsOOBWorkflow = IsOOBWorkflow(workflowTemplate.Id.ToString()), SubscriptionId = associatedWorkflow.WorkflowAssociation.Id, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2010 {associatedWorkflow.Scope} type workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } else { // Only add non OOB workflow templates when there's no associated workflow - makes the dataset smaller if (!IsOOBWorkflow(workflowTemplate.Id.ToString())) { WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = "", ListUrl = "", ListId = Guid.Empty, ContentTypeId = "", ContentTypeName = "", Version = "2010", Scope = "", RestrictToType = "N/A", DefinitionName = workflowTemplate.Name, DefinitionDescription = workflowTemplate.Description, SubscriptionName = "", HasSubscriptions = false, Enabled = false, DefinitionId = workflowTemplate.Id, IsOOBWorkflow = IsOOBWorkflow(workflowTemplate.Id.ToString()), SubscriptionId = Guid.Empty, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2010 type workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } } // Are there associated workflows for which we did not find a template foreach (var associatedWorkflow in this.sp2010WorkflowAssociations) { if (!processedWorkflowAssociations.Contains(associatedWorkflow.WorkflowAssociation.Id)) { // Skip previous versions of a workflow // todo: non-english sites will use another string if (associatedWorkflow.WorkflowAssociation.Name.Contains("(Previous Version:")) { continue; } WorkflowScanResult workflowScanResult = new WorkflowScanResult() { SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, ListTitle = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Title : "", ListUrl = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.RootFolder.ServerRelativeUrl : "", ListId = associatedWorkflow.AssociatedList != null ? associatedWorkflow.AssociatedList.Id : Guid.Empty, ContentTypeId = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.StringId : "", ContentTypeName = associatedWorkflow.AssociatedContentType != null ? associatedWorkflow.AssociatedContentType.Name : "", Version = "2010", Scope = associatedWorkflow.Scope, RestrictToType = "N/A", DefinitionName = associatedWorkflow.WorkflowAssociation.Name, DefinitionDescription = "", SubscriptionName = associatedWorkflow.WorkflowAssociation.Name, HasSubscriptions = true, Enabled = associatedWorkflow.WorkflowAssociation.Enabled, DefinitionId = Guid.Empty, IsOOBWorkflow = false, SubscriptionId = associatedWorkflow.WorkflowAssociation.Id, }; if (!this.ScanJob.WorkflowScanResults.TryAdd($"workflowScanResult.SiteURL.{Guid.NewGuid()}", workflowScanResult)) { ScanError error = new ScanError() { Error = $"Could not add 2010 {associatedWorkflow.Scope} type workflow scan result for {workflowScanResult.SiteColUrl}", SiteColUrl = this.SiteCollectionUrl, SiteURL = this.SiteUrl, Field1 = "WorkflowAnalyzer", }; this.ScanJob.ScanErrors.Push(error); } } } } finally { this.StopTime = DateTime.Now; } // return the duration of this scan return(new TimeSpan((this.StopTime.Subtract(this.StartTime).Ticks))); }