Contact.ExpandGroups method (SPWeb, Contact[], Int32, Boolean)

Recursively expands any security groups, SharePoint groups, or distribution lists into individual users, given an array of Contact objects.

Namespace:  Microsoft.Office.Workflow.Utility
Assembly:  Microsoft.Office.Workflow.Tasks (in Microsoft.Office.Workflow.Tasks.dll)

Syntax

'Declaration
Public Shared Function ExpandGroups ( _
    web As SPWeb, _
    reviewers As Contact(), _
    maxCount As Integer, _
    <OutAttribute> ByRef reachedMaxCount As Boolean _
) As Contact()
'Usage
Dim web As SPWeb
Dim reviewers As Contact()
Dim maxCount As Integer
Dim reachedMaxCount As Boolean
Dim returnValue As Contact()

returnValue = Contact.ExpandGroups(web, _
    reviewers, maxCount, reachedMaxCount)
public static Contact[] ExpandGroups(
    SPWeb web,
    Contact[] reviewers,
    int maxCount,
    out bool reachedMaxCount
)

Parameters

  • reviewers
    Type: []

    Array of Contact objects to expand.

  • maxCount
    Type: System.Int32

    The maximum number of users to expand to.

  • reachedMaxCount
    Type: System.Boolean

    If highest member count is reached, then true; otherwise, false.

Return value

Type: []
Array of Contact objects representing individual users.

Remarks

The array is a string array.

The following sample uses a Contact Selector ActiveX control in a Microsoft Office InfoPath 2007 form and uses the System.Workflow.Activities.ReplicatorActivity class to create tasks for each user, either sequentially or in parallel depending on what the user specifies in the form. It also uses the WssTaskActivity class from the ECMActivities sample to manage tasks inside the Replicator activity.

Form data is parsed by using the Contact and [Microsoft.Office.Workflow.Utility.Form] classes.

For more information about this sample, see the ApprovalWorkflowSample code sample folder and EnterpriseContentManagementStarterKitSampleGuide document in the ECM Starter Kit folder in the Microsoft Office SharePoint Server 2007 SDK download.

Examples

/**********************************************************************
 * Approval Sample Workflow
 * This workflow is a sample approval workflow. This workflow 
 * demonstrates how to use a serial/parallel workflow, workflow 
 * modifications, history events, and custom states. **********************************************************************/

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Xml;
using System.Xml.Serialization;

using Microsoft.Office.Workflow.Utility;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;
using Microsoft.Office.Samples.ECM.Activities;

namespace Microsoft.Office.Samples.ECM.Workflow
{
    /// <summary>
    /// A sample approval workflow.
    /// </summary>
      public sealed partial class ApprovalWorkflowSample: SequentialWorkflowActivity
    {
        #region Public Members

        // Identifier.
        public Guid workflowId = Guid.Empty;

        // Workflow properties.
        public int workflowState = -1;
        public bool isWorkflowCanceled = false;
        public bool isWorkflowRejected = false;
        public SPWorkflowActivationProperties workflowProperties = new SPWorkflowActivationProperties();

        // Replicator data.
        public IList replicatorInitialChildData = null;
        public ExecutionType replicatorExecutionType = ExecutionType.Sequence;

        // Task data.
        public Hashtable initFormData = null;
        public Contact workflowOriginator = null;
        public string workflowName = null;
        public string itemDisplayName = null;        

        // Add approver modification.
        public string addApproverData = null;
        public int addApproverUser = default(int);
        public string addApproverHistoryDescription = null;

        // Cancel workflow modification data.
        public string cancelWorkflowData = null;
        public string cancelWorkflowUser = null;
        public SPWorkflowTaskProperties cancelTaskProperties = null;

        // Update task modification data.
        public string updateTaskData = null;
        public int updateTaskUser = default(int);
        public string updateTaskHistoryDescription = null;
        public SPWorkflowTaskProperties updateTaskProperties = null;

        public const int MaxExpansionCount = 100;

        #endregion

        #region Public Constructors

        /// <summary>
        /// Default constructor.
        /// </summary>
        public ApprovalWorkflowSample()
        {
            InitializeComponent();
        }

        #endregion

        #region Workflow Methods

        /// <summary>
        /// Initializes the workflow. 
        /// Parse the data from the form XML string by using the 
        /// Form and Contact classes in the 
        /// Microsoft.Office.Workflow.Utility namespace.
        /// </summary>
        private void OnWorkflowActivated(object sender, ExternalDataEventArgs e)
        {
            // Convert the initiation form data to a hash table.
            this.initFormData = Form.XmlToHashtable(this.workflowProperties.InitiationData);

            // Retrieve the add approver form data.
            this.addApproverData = this.workflowProperties.Workflow.ParentAssociation["Modification_af71f77b-e6c8-483a-acbf-30b4a84bd209_Data"] as string;

            // Retrieve the cancel workflow form data.
            this.cancelWorkflowData = this.workflowProperties.Workflow.ParentAssociation["Modification_b21bcc38-2c23-4b64-8e7d-c32c43ad42cf_Data"] as string;

            // Retrieve the update all tasks form data.
            this.updateTaskData = this.workflowProperties.Workflow.ParentAssociation["Modification_27f914ae-3112-493d-86cf-43dc93870418_Data"] as string;

            // Get the originator of the workflow.
            this.workflowOriginator = Contact.FromPrincipal(this.workflowProperties.OriginatorUser);
            this.workflowName = this.workflowProperties.Workflow.ParentAssociation.Name;
            this.itemDisplayName = this.workflowProperties.Item.DisplayName;

            // Set the replicator execution type.
            this.replicatorExecutionType = (this.initFormData["CreateTasksInSerial"] as string) == "true" ?
                ExecutionType.Sequence : ExecutionType.Parallel;

            // Use the built-in methods to parse out the people.
            Contact[] contacts = Contact.ToContacts((this.initFormData["Reviewers"] as string), this.workflowProperties.Web);

            // Determine whether to expand the groups that were 
            // entered into the form.
            if ((this.initFormData["GroupTasks"] as string) == "false")
            {
                // Expand the groups (recursively).
                bool reachedMaxCount = false;
                contacts = Contact.ExpandGroups(this.workflowProperties.Web, contacts,
                    ApprovalWorkflowSample.MaxExpansionCount, out reachedMaxCount);
            }

            // Add the data to the replicator so that you can 
            // generate a task for each contact.
            // The replicator will create a child for each item 
            // in the list.
            this.replicatorInitialChildData = new List<SPWorkflowTaskProperties>();

            // Determine the due date of the task (if any).
            DateTime dueDate = DateTime.MinValue;

            // Due date from a serial approval workflow.
            if ((this.replicatorExecutionType == ExecutionType.Sequence) && (!string.IsNullOrEmpty(this.initFormData["TimePerTaskVal"] as string)))
            {
                dueDate = DateTime.Now.AddDays(Convert.ToInt32(this.initFormData["TimePerTaskVal"]));
            }
            // Due date from a parallel approval workflow.
            else if ((this.replicatorExecutionType == ExecutionType.Parallel) && (!string.IsNullOrEmpty(this.initFormData["DueDate"] as string)))
            {
                dueDate = Convert.ToDateTime(this.initFormData["DueDate"]);
            }            

            // Prepare the replicator data.
            string contactNames = "";

            foreach (Contact contact in contacts)
            {
                contactNames += string.Format(" {0},", contact.DisplayName);

                SPWorkflowTaskProperties taskProperties = new SPWorkflowTaskProperties();
                taskProperties.AssignedTo = contact.LoginName;
                taskProperties.Description = string.IsNullOrEmpty(this.initFormData["Description"] as string) ? "No instructions were provided." : this.initFormData["Description"] as string;
                taskProperties.DueDate = dueDate;

                this.replicatorInitialChildData.Add(taskProperties);
            }

            // Trim the trailing ',' from the string.
            contactNames = contactNames.Substring(0, contactNames.Length - 1);

            // Set the Windows SharePoint Services user ID 
            // of the workflow originator.
            int workflowOriginatorId = this.workflowOriginator.PrincipalID;

            // Set up the "workflow started" history data.
            this.logWorkflowStartedHistoryActivity.HistoryDescription = string.Format("{0} was started.  Participants:{1}",
                this.workflowName, contactNames);
            this.logWorkflowStartedHistoryActivity.UserId = workflowOriginatorId;

            // Set up the "workflow completed" history data.
            this.logWorkflowCompletedHistoryActivity.HistoryDescription = string.Format("{0} was completed.", this.workflowName);
            this.logWorkflowCompletedHistoryActivity.HistoryOutcome = string.Format("{0} on {1} has successfully completed. All participants have completed their tasks.",
                this.workflowName, this.itemDisplayName);
            this.logWorkflowCompletedHistoryActivity.UserId = workflowOriginatorId;
        }

        /// <summary>
        /// Initializes and creates the tasks for each replicator child 
        /// (see the WssTaskActivity class in the 
        /// Microsoft.Office.Samples.ECM.Activities sample 
        /// for details).
        /// </summary>
        private void OnReplicatorChildInitialized(object sender, ReplicatorChildEventArgs e)
        {
            // Retrieve the activities.
            WssTaskActivity task = e.Activity as WssTaskActivity;            
            task.createWssTask_TaskProperties1 = (SPWorkflowTaskProperties)e.InstanceData;

            // Set up the task.
            task.TaskId = Guid.NewGuid();

            // Add the user to the infotable in Windows SharePoint 
            // Services (if possible) and 
            // allows checking for users that cannot 
            // be assigned a task.
            Contact contact = Contact.FromName(task.createWssTask_TaskProperties1.AssignedTo, this.workflowProperties.Web);
            SPPrincipal contactPrincipal = contact.GetPrincipal(this.workflowProperties.Web);

            if ((contactPrincipal == null) || (contactPrincipal.ID == -1))
            {
                // Cannot assign the task to this principal,
                // so try the workflow owner.
                task.createWssTask_TaskProperties1.AssignedTo = this.workflowOriginator.LoginName;
                task.createWssTask_TaskProperties1.Title = string.Format("Please approve {0} (external participant)", this.itemDisplayName);
                task.createWssTask_TaskProperties1.OnBehalfEmail = contact.EmailAddress;
                task.createWssTask_TaskProperties1.OnBehalfReason = "This user does not have access to this Windows SharePoint Server site.";
                task.TaskAssignedTo = this.workflowOriginator;
            }
            else
            {
                // Normal task.
                task.createWssTask_TaskProperties1.Title = string.Format("Please approve {0}", this.itemDisplayName);
                task.TaskAssignedTo = contact;
            }

            // Update the unescaped description text, 
            // which is displayed in the Infopath form.
    task.createWssTask_TaskProperties1.ExtendedProperties["BodyText"] = SPHttpUtility.HtmlDecode(task.createWssTask_TaskProperties1.Description);

            // Set the remaining values.
            task.createWssTask_TaskProperties1.PercentComplete = 0;
            task.createWssTask_TaskProperties1.SendEmailNotification = true;
            task.createWssTask_TaskProperties1.StartDate = DateTime.Now;
            task.createWssTask_TaskProperties1.TaskType = Convert.ToInt32(this.initFormData["DefaultTaskType"]);
            
            // Set the "created by" user.
            if (task.TaskCreatedBy == null)
            {
                task.TaskCreatedBy = this.workflowOriginator;
            }
        }
        
        /// <summary>
        /// Determine if the replicator is done yet.
        /// </summary>
        private void IsReplicatorDone(object sender, ConditionalEventArgs e)
        {
            // Check if all the replicator children have completed.
            e.Result = this.taskReplicator1.AllChildrenComplete;
        }

        /// <summary>
        /// Handler for WssTaskActivity.IsTaskCompleted property, 
        /// which is fired when a task is changed.
        /// TaskEventArgs contains the 
        /// SPWorkflowTaskProperties (AfterProperties) that
        /// contain the new values in the task that changed.  
        /// The task is complete if the Completed field is true.
        /// </summary>
        private void IsTaskCompleted(object sender, TaskEventArgs e)
        {
            // Get the task data.
            WssTaskActivity task = sender as WssTaskActivity;

            // Get the name of the user who performed this action.
            Contact contact = Contact.FromName(e.Executor, this.workflowProperties.Web);

            // Get the task result - # and @ in the "TaskStatus" field 
            // indicate a completion.
            string taskResult = e.AfterProperties.ExtendedProperties["TaskStatus"] as string;

            bool isTaskRejected = (taskResult == "@");
            e.Result = isTaskRejected || (taskResult == "#");                       

            // Single rejection rejects the entire workflow.
            if (isTaskRejected)
            {
                this.isWorkflowRejected = true;
            }

            // Checks if the task is completed.
            if (e.Result)
            {
                task.TaskChangedBy = contact;
                task.TaskCompletedBy = contact;
                task.completeWssTask_TaskOutcome1 = isTaskRejected ? string.Format("Task rejected by {0}", contact.DisplayName) : 
                    string.Format("Task approved by {0}", contact.DisplayName);                
            }
        }

        /// <summary>
        /// Handle a deleted task.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnTaskDeleted(object sender, TaskEventArgs e)
        {
            // Get the task.
            WssTaskActivity task = sender as WssTaskActivity;

            // Get the name of the user who performed this action.
            Contact contact = Contact.FromName(e.Executor, this.workflowProperties.Web);

            // Set the friendly name.
            task.TaskDeletedBy = contact;

            // Reject the workflow.
            this.isWorkflowRejected = true;
        }

        #endregion        
     
        #region Add Approver Modification

        /// <summary>
        /// This modification adds 1 or more approvers to the workflow.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAddApprover(object sender, ExternalDataEventArgs e)
        {
            Hashtable data = Form.XmlToHashtable(this.addApproverData);

            // Get the user who added a new user or users to the workflow.
            Contact identity = Contact.FromName(e.Identity, this.workflowProperties.Web);

            // Get the new user or users to add.
            Contact[] contacts = Contact.ToContacts((data["NewReviewers"] as string), this.workflowProperties.Web);

            // Prepare the replicator data.
            string contactNames = "";

            foreach (Contact contact in contacts)
            {
                contactNames += string.Format(" {0},", contact.DisplayName);

                SPWorkflowTaskProperties taskProperties = new SPWorkflowTaskProperties();
                taskProperties.AssignedTo = contact.LoginName;
                taskProperties.Description = string.IsNullOrEmpty(data["Description"] as string) ? "No instructions were provided." : data["Description"] as string;
                taskProperties.DueDate = string.IsNullOrEmpty(data["DueDate"] as string) ? DateTime.MinValue : Convert.ToDateTime(data["DueDate"] as string);

                // Add the task to the replicator.                this.taskReplicator1.CurrentChildData.Add(taskProperties);
            }
            
            // Trim the trailing ',' from the string.
            contactNames = contactNames.Substring(0, contactNames.Length - 1);

            // Set the description for the history event.
            this.addApproverHistoryDescription = string.Format("Participants for {0} on {1} were updated by {2}. New participants:{3}",
                this.workflowName, this.workflowProperties.Item.DisplayName, identity.DisplayName, contactNames);

            // Set the approver Windows SharePoint Services user ID.
            this.addApproverUser = identity.PrincipalID;
        }

        #endregion

        #region Cancel Workflow Modification

        /// <summary>
        /// Cancels a workflow.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnWorkflowCanceled(object sender, ExternalDataEventArgs e)
        {
            // Get the identity of the person canceling the workflow.
            SPModificationEventArgs args = e as SPModificationEventArgs;
            Contact identity = Contact.FromName(e.Identity, this.workflowProperties.Web);

            //  the workflow as canceled.
            this.isWorkflowCanceled = true;

            // Update the completed history outcome.
            this.logWorkflowCompletedHistoryActivity.HistoryOutcome = 
                string.Format("{0} on {1} has ended because {2} has canceled the workflow.",
                this.workflowName, this.itemDisplayName, identity.DisplayName);

            // End the replicator - this is handled by the 
            // event that handles the scope fault handler.
            throw new CancelApprovalWorkflowSampleException();            
        }

        /// <summary>
        /// Mark all the existing tasks as complete.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateAllTasksOnCancel(object sender, EventArgs e)
        {
            // Set all active tasks to completed.
            this.cancelTaskProperties = new SPWorkflowTaskProperties();
            this.cancelTaskProperties.ExtendedProperties[SPBuiltInFieldId.Completed] = true;
            this.cancelTaskProperties.ExtendedProperties[SPBuiltInFieldId.TaskStatus] = "Completed";
            this.cancelTaskProperties.ExtendedProperties[SPBuiltInFieldId.WorkflowOutcome] = "Canceled by the workflow";
        }

        #endregion        

        #region Update All Tasks Modification

        /// <summary>
        /// Update all active tasks.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnUpdateAllTasks(object sender, ExternalDataEventArgs e)
        {
            Hashtable data = Form.XmlToHashtable(this.updateTaskData);
            this.updateTaskProperties = new SPWorkflowTaskProperties();

            // Get the user who updated the tasks.
            Contact identity = Contact.FromName(e.Identity, this.workflowProperties.Web);

            // Fetch the due date and store it for future tasks.
            string dueDate = data["DueDate"] as string;

            // Fetch the description and store it for future tasks.
            this.updateTaskProperties.Description = 
                string.IsNullOrEmpty(data["Description"] as string) ? "No instructions were provided." : data["Description"] as string;

            // Update the unescaped text, which is displayed 
            // in the Infopath form.
            this.updateTaskProperties.ExtendedProperties["BodyText"] =
                SPHttpUtility.HtmlDecode(this.updateTaskProperties.Description);

            // Set the DateTime field, and then repurpose 
            // the dueDate variable.
            if (string.IsNullOrEmpty(dueDate))
            {
                this.updateTaskProperties.DueDate = DateTime.MinValue;
                dueDate = "None";
            }
            else
            {
                this.updateTaskProperties.DueDate = Convert.ToDateTime(dueDate);
                dueDate = this.updateTaskProperties.DueDate.ToShortDateString();
            }

            // Update the future tasks.
            foreach (SPWorkflowTaskProperties taskProperties in this.taskReplicator1.CurrentChildData)
            {
                taskProperties.Description = this.updateTaskProperties.Description;
                taskProperties.DueDate = this.updateTaskProperties.DueDate;
            }

            // Set the description for the history event.
            this.updateTaskHistoryDescription = string.Format("Tasks for {0} on {1} were updated by {2}. Due by: {3} Task instructions: {4}",
                this.workflowName, this.workflowProperties.Item.DisplayName, identity.DisplayName, dueDate, this.updateTaskProperties.Description);

            // Set the Windows SharePoint Services user ID 
            // of the person who updated the tasks.
            this.updateTaskUser = identity.PrincipalID;
        }

        #endregion

        #region Set State

        /// <summary>
        /// Sets the state (Canceled, Rejected, Approved) of the 
        /// workflow. States are set in the workflow template file
        /// under Metadata/ExtendedStatusValues.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SetWorkflowState(object sender, EventArgs e)
        {
            // Checks if the workflow is canceled.
            if (this.isWorkflowCanceled)
            {
                this.workflowState = (int)SPWorkflowStatus.Max;
            }
            // Checks if the workflow is rejected.
            else if (this.isWorkflowRejected)
            {
                this.workflowState = (int)SPWorkflowStatus.Max + 2;
            }
            // The workflow is approved.
            else
            {
                this.workflowState = (int)SPWorkflowStatus.Max + 1;
            }
        }

        #endregion
    }

    public class CancelApprovalWorkflowSampleException : Exception
    { }
}

See also

Reference

Contact class

Contact members

ExpandGroups overload

Microsoft.Office.Workflow.Utility namespace