Generate Unique Attribute Workflow Using Enumerate Resources Activity

When generating attributes in the FIM Portal, it’s a common requirement to ensure that the value of the attribute you’re generating is unique. This example demonstrates how you can use the Enumerate Resources Activity to query the FIM Portal to detect if the value already exists and, if it does, to generate a new value.

As username is a fairly common attribute that needs to be generated, this example will demonstrate a simple way in which you can generate a unique username in the FIM Portal. Specifically, this example will take a first name and a surname, generate a username in the format of <first initial><surname> and check whether it exists in the FIM Portal. If it does, a number will be added to the end, which will be incremented and checked until a unique value is discovered.

Ways to extend this may include providing input arguments into the Workflow which will allow you to pre-generate possible usernames, or to alternatively provide a variety of alternatives which the Workflow could return, rather than just incrementing a counter on the end of the value. You might also expand this Workflow to specify which attribute you’re writing to, rather than just assuming the AccountName attribute.

Step 1: Add a new Activity to your FIM Custom Activity Library


Step 2: Give that activity an appropriate name, in this case GenerateUniqueUsername.cs


Step 3: Add the relevant activities into the workflow. In this example, we are using the Current Request Activity, a Code Activity, a Read Resource Activity, an Enumerate Resources Activity, an Update Resource Activity and a While Activity.


Step 4: Give these activities meaningful names so that we can refer to them easily in code.


Step 5: Double click the code activity to promote the bindable properties for the code blocks. Also, create a function that we can call from the while activity to determine whether the activity should execute again. This second function will not only keep the while loop going, but will also initialise the update activity when a unique value has been found.


Step 6: Under properties, change the While Activity’s Condition to “Code Condition” and bind it to the function we’ve just created.


Step 7: Add the Microsoft.ResourceManagement references to your activity, as well as System.Collections.Generic so we can use the List Class.


Step 8: Write the code that will read out the Firstname and Lastname of the current user. In this example, we are using the Current Request Activity to get the Resource ID of the user that we wish to query, however we could also omit this activity and get the current request’s target ID from the containing workflow (using SequentialWorkflow.TryGetContainingWorkflow(this, out containingWorkflow)).


Step 9a: Populate the While Code Condition code. There are three key segments here. The first part, is for the first entry into the loop. It generates the base username using the user’s firstname and lastname, then sets an XPathFilter attribute on the Enumerate Resources Activity that will search the FIM Portal database to see if there is a matching username.


Step 9b: The second part of the while condition code checks to see if any users were returned by the Enumerate Resources Activity. In this example, we’re not using the results, just checking to see if any were returned. If there were any returned, then the account name is not unique, so a new one is generated, the Enumerate Resources Activity XPathFilter is configured to search for the new account name, and loop condition is set to true.


Step 9c: Once the Enumerate Resources Activity returns 0 results, we can consider that the account name that has been generated is unique and so we can configure the Update Resource Activity to update the user with the generated account name, and to tell the while activity that it can stop looping.


Step 10: The last line of the while condition code sets the loop condition.


Step 11: Add another class to the project to create a basic UI for this activity, with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.ResourceManagement.Workflow.Activities;
using Microsoft.IdentityManagement.WebUI.Controls;
using FIMSpecialist.DemoLibrary.Activities;

namespace FIMSpecialist.DemoLibrary.WebUIs
{
    public class GenerateUniqueUsernameUI : ActivitySettingsPart
    {
        public override System.Workflow.ComponentModel.Activity GenerateActivityOnWorkflow(SequentialWorkflow workflow)
        {
            if (!this.ValidateInputs())
            {
                return null;
            }

            GenerateUniqueUsername usernameGenerator = new GenerateUniqueUsername();
            return usernameGenerator;
        }

        public override void LoadActivitySettings(System.Workflow.ComponentModel.Activity activity)
        {
            GenerateUniqueUsername usernameGenerator = activity as GenerateUniqueUsername;

            if (null != usernameGenerator)
            {
            }
        }

        public override ActivitySettingsPartData PersistSettings()
        {
            ActivitySettingsPartData data = new ActivitySettingsPartData();
            return data;
        }

        public override void RestoreSettings(ActivitySettingsPartData data)
        {
            if (null != data)
            {
            }
        }

        public override void SwitchMode(ActivitySettingsPartMode mode)
        {
            bool readOnly = mode == ActivitySettingsPartMode.View;
        }

        public override string Title
        {
            get { return "Create Unique Username"; }
        }

        public override bool ValidateInputs()
        {
            return true;
        }
    }
}

And that’s it. From here, configure your Workflow in the FIM Portal, and setup an MPR to execute the workflow when a user is created.

Final code for GenerateUniqueUsername.cs is as follows:

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Linq;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Microsoft.ResourceManagement.WebServices.WSResourceManagement;
using Microsoft.ResourceManagement.Workflow.Activities;
using Microsoft.ResourceManagement.WebServices;
using System.Collections.Generic;

namespace FIMSpecialist.DemoLibrary.Activities
{
    public partial class GenerateUniqueUsername : SequenceActivity
    {
        // This is the guid for the FIM Service built-in admin account.
        // We will use it to execute each of our workflow activities.
        const string FIMAdminGuid = "7fb2b853-24f0-4498-9534-4e10589723c4";
        string baseAccountName;
        string accountName;
        int loopValue = 1;

        public GenerateUniqueUsername()
        {
            InitializeComponent();
        }

        private void InitialiseReadUser_ExecuteCode(object sender, EventArgs e)
        {
            // Set the Actor ID for the Read Activity to the FIM Admin GUID
            ReadUser.ActorId = new Guid(FIMAdminGuid);

            // Set the Resource to retrieve as the currently requested object.
            // Note, you could also set this to the target ID of the containing workflow
            ReadUser.ResourceId = CurrentRequest.CurrentRequest.Target.GetGuid();

            // Set the selection parameters as the first name and last name attributes
            ReadUser.SelectionAttributes = new string[] { "FirstName", "LastName" };

        }

        public void isUnique(object sender, ConditionalEventArgs e)
        {
            bool keepLooping = true;

            // On the first pass, set the base username and search
            if (baseAccountName == null)
            {
                // Get the object that was read using the Read Activity Resource into a ResourceType object
                // In a real-world workflow, you would check that anything was returned at all.
                ResourceType user = ReadUser.Resource;

                // Get the first name and last name from this object.
                // again, in a real-world workflow, you should check that these values are not null.
                string firstname = (string)user["FirstName"];
                string lastName = (string)user["LastName"];

                // build the base username in format , eg RCurrie
                baseAccountName = firstname.Substring(0, 1) + lastName;
                accountName = baseAccountName;

                // Initialise the search for the base username
                SearchForUsername.ActorId = new Guid(FIMAdminGuid);
                SearchForUsername.XPathFilter = String.Format("/Person[AccountName='{0}'",accountName);

                // set the while condition to true so it executes the search the first time.
                keepLooping = true;
            }
            else // we've already searched for the user 
            {
                // If any results were returned
                if (SearchForUsername.TotalResultsCount > 0)
                {
                    // Create the alternative username and increment the value.
                    accountName = baseAccountName + loopValue++;

                    // Initialise the search for the base username
                    SearchForUsername.ActorId = new Guid(FIMAdminGuid);
                    SearchForUsername.XPathFilter = String.Format("/Person[AccountName='{0}'", accountName);

                    // instruct the while activity to loop again
                    keepLooping = true;
                }
                // Otherwise, the value was unique, update the record
                else
                {
                    // Set the actor ID and the resource ID again.
                    UpdateUser.ActorId = new Guid(FIMAdminGuid);
                    UpdateUser.ResourceId = CurrentRequest.CurrentRequest.Target.GetGuid();

                    // Create a list of UpdateRequestParameter objects
                    List updateRequestParameters = new List();

                    // Add the AccountName and DisplayName we generated as parmaters to update on the object
                    updateRequestParameters.Add(new UpdateRequestParameter("AccountName", UpdateMode.Insert, accountName));

                    // Convert the update parameters list into an array of UpdateRequestParameter objects and assign it
                    // to the UpdateParameters property of the Update Resource Activity
                    UpdateUser.UpdateParameters = updateRequestParameters.ToArray();

                    // instruct the while activity it is done looping
                    keepLooping = false;
                }
            }

            e.Result = keepLooping;
        }

    }

}