Walkthrough: Create a Logging Custom Activity and Deploy it to the FIM Portal

This topic explains how to create a Forefront Identity Manager (FIM) custom activity that logs details about a request to a log file. It also explains how to create a user interface (UI) for the activity so that a FIM administrator can add the activity to a workflow using the FIM Workflow Designer. The administrator can use the UI to specify the log file location and log file name of the activity.

Warning

You must start and stop the FIM Service every time that you upload an activity to FIM. Therefore, you should thoroughly test custom activities on a test system before using them in a production environment. In addition, if your activity has a UI, you must also run IISRESET when you add or modify the UI of the activity to see the updated UI in the FIM Portal.

Warning

When developing custom activities or workflows you must ensure that you are using .NET 3.5. The FIMService will only work with .NET 3.5. The default option in Visual Studio 2010 is .NET Framework 4. The default option in Visual Studio 2012 is .NET Framework 4.5.

Creating a Workflow Activity Library and Activity in Visual Studio

The following procedure shows how to create a new workflow activity library in Visual Studio 2008. This library can contain one or more custom activities.

To create a new workflow activity library and activity file

  1. Start Visual Studio 2008.

  2. Create a new Workflow Activity Library. This example uses Visual C#, but you can also use Visual Basic.

    • On the File menu, point to New, and then click Project.

    • Under the Visual C# project types, select Workflow. In the Templates pane, select Workflow Activity Library.

    • Clear the Create directory for solution check box.

    • Name the library LoggingActivitiesLibrary, and then click OK.

    The following illustration shows the New Project dialog box after you make these selections.

    Create the project in Visual Studio

  3. Add references to the FIM assemblies:

    • If Solution Explorer is not open, on the View menu, click Solution Explorer.

    • In Solution Explorer, right-click the References folder, and then click Add Reference.

    • In the Add Reference dialog box, click the Browse tab, and browse to folder that contains the Microsoft.ResourceManagement.dll assembly.

      Tip

      The default installation location for this assembly is C:\Program Files\Microsoft Forefront Identity Manager\2010\Service.

    • Select the Microsoft.ResourceManagement.dll assembly, and then click OK.

  4. Set the application properties:

    • In Solution Explorer, right-click the Properties folder, and then click Open.

      This action opens a project properties file in the workspace that has the same name as your project. In this example, the file is named LoggingActivitiesLibrary.

    • Click the Application tab.

    • In Assembly name, type LoggingActivitesLibrary.

    • In Default namespace, type FIM.CustomWorkflowActivitiesLibrary.Activities.

    • In Target Framework, select .Net Framework 3.5.

    • Close the LoggingActivitiesLibrary file.

  5. Create a new activity called RequestLoggingActivity:

    • In Solution Explorer, right-click LoggingActivitiesLibrary, click Add, and then click Activity.

    • Name the new activity RequestLoggingActivity.cs.

    • Click Add.

    Two new files appear in Solution Explorer: RequestLoggingActivity.cs and RequestLoggingActivity.Designer.cs.

  6. In Solution Explorer, right-click Activity1.cs, and then click Delete.

  7. To save the project, on the File menu, click Save All.

  8. To build the project, on the Build menu, click Solution.

You now have a Visual Studio project that contains references to FIM assemblies. It also contains two files that you will use to define the new activity: RequestLoggingActivity.cs and RequestLoggingActivity.Designer.cs.

Next, you add FIM out-of-box activities to the Toolbox so that you can specify the sequence of activities that define the custom activity.

Adding FIM Out-of-Box Activities to the Toolbox

The following procedure shows how to add the FIM out-of-box activities to the Toolbox. Skip this procedure if there is already a tab in the Toolbox of your project that contains all the FIM activities from the ResourceManagement.dll assembly.

To create a FIM Activities tab in the toolbox

  1. If the RequestLoggingActivity.cs [Design] tab is not already open, right-click the RequestLoggingActivity.cs file in Solution Explorer, and then click View Designer.

  2. Click the RequestLoggingActivity.cs [Design] tab.

  3. If the Toolbox is not open, on the View menu, click Toolbox.

  4. Right-click Toolbox, and then click Add Tab. Rename the new tab FIM Activities.

  5. Right-click the new tab, and then click Choose Items.

  6. In the Choose Toolbar Items dialog box, click the Activities tab.

    Note

    If you are using Visual Studio 2010, be aware that Activities is now System.Workflow.Components.

    Be aware that in Visual Studio 2012 there is no Activities tab or System.Workflow Components tab. To work around this, simply drag Microsoft.ResourceManagement.dll into the area under the FIM Activites tab that was created in step 4. You should now see the activities in the toolbox.

  7. Click Clear to remove other activities from the tab.

  8. Click Browse, and then browse to the Microsoft.ResourceManagement.dll assembly that is installed with FIM.

  9. Select each check box next to the activities in the Microsoft.ResourceManagement.Workflows.Activities namespace that you want to include in the Toolbox. For information about which activities are supported, see Microsoft.ResourceManagement.Workflow.Activities.

  10. Click OK.

You now have a FIM Activities tab in the Toolbox. You can specify the sequence of activities that define your custom activity by dragging and dropping FIM activities from the Toolbox onto the design surface of that activity.

Defining the activity sequence and properties

Now, you can define properties that will store the log file path and log file name where the activity will log information about the current request and workflow dictionary information.

Tip

Each custom activity should be designed as a stand-alone component with its own set of input and output parameters. When the activity is designed this way, it is decoupled from all other activities and any host workflow that could use it. To support the binding of properties in this activity to other activities, dependency properties are used instead of normal .Net properties.

The following table describes the three properties that you add for the RequestLoggingActivity.

Property name Description

LogFilePath

This property is an input property indicating the folder where the log file should be saved.

LogFileName

This property is an input property indicating the log file name.

CurrentRequest

This property is used to store information about the current request.

The input properties will be displayed in the Workflow Designer in the FIM Portal when the activity is added to a workflow. The user that adds the activity to the workflow must enter values for these properties.

To define properties for the RequestLoggingActivity

  1. Right-click RequestLoggingActivity.cs in Solution Explorer, and then click View Code.

  2. Add the following using statements to the top of the file:

    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    //The following two namespaces define the FIM object model
    using Microsoft.ResourceManagement.WebServices.WSResourceManagement;
    using Microsoft.ResourceManagement.Workflow.Activities;
    
  3. Add the following code to RequestLoggingActivity.cs after the class constructor:

    #region Public Workflow Properties
    
    public static DependencyProperty ReadCurrentRequestActivity_CurrentRequestProperty = DependencyProperty.Register("ReadCurrentRequestActivity_CurrentRequest", typeof(Microsoft.ResourceManagement.WebServices.WSResourceManagement.RequestType), typeof(FIM.CustomWorkflowActivitiesLibrary.Activities.RequestLoggingActivity));
    
    /// <summary>
    ///  Stores information about the current request
    /// </summary>
    [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
    [BrowsableAttribute(true)]
    [CategoryAttribute("Misc")]
    public RequestType ReadCurrentRequestActivity_CurrentRequest
    {
         get
         {
              return ((Microsoft.ResourceManagement.WebServices.WSResourceManagement.RequestType) (base.GetValue(FIM.CustomWorkflowActivitiesLibrary.Activities.RequestLoggingActivity.ReadCurrentRequestActivity_CurrentRequestProperty)));
         }
         set
         {        base.SetValue(FIM.CustomWorkflowActivitiesLibrary.Activities.RequestLoggingActivity.ReadCurrentRequestActivity_CurrentRequestProperty, value);
         }
    }
    
    /// <summary>
    ///  Identifies the Log File Path
    /// </summary>
    public static DependencyProperty LogFilePathProperty = DependencyProperty.Register("LogFilePath", typeof(System.String), typeof(RequestLoggingActivity));
    [Description("Please specify the Log File Path")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [Browsable(true)]
    public string LogFilePath
    {
        get
        {
            return ((String)(base.GetValue(RequestLoggingActivity.LogFilePathProperty)));
        }
        set
        {
            base.SetValue(RequestLoggingActivity.LogFilePathProperty, value);
        }
    }
    
    /// <summary>
    ///  Identifies the Log File Name
    /// </summary>
    public static DependencyProperty LogFileNameProperty = DependencyProperty.Register("LogFileName", 
         typeof(System.String), typeof(RequestLoggingActivity));
    [Description("Please specify the Log File Path")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [Browsable(true)]
    public string LogFileName
    {
        get
        {
            return ((String)(base.GetValue(RequestLoggingActivity.LogFileNameProperty)));
        }
        set
        {
            base.SetValue(RequestLoggingActivity.LogFileNameProperty, value);
        }
    }
    #endregion
    

You have now defined three properties that you can use in your custom activity.

Note

You do not have to remember the source code for creating a DependencyProperty. You can simply insert the code by using Visual Studio Code Snippet. Right-click in the code and click Insert Snippet, click Other, click Workflow, and then click DependencyProperty - Property.

Next, you define a sequence of activities that define your activity by using activities from the Toolbox. You use the FIM CurrentRequestActivity activity and the Code activity that is defined by Windows Workflow Foundation (WF). You also bind the CurrentRequest property that you added in the code to a property of the ReadCurrentRequestActivity that you will create.

To define the activity sequence

  1. Click the RequestLoggingActivity.cs [Design] tab on the workspace to open the workflow designer sheet of the RequestLoggingActivity custtom activity. If the tab does not exist, double-click RequestLoggingActivity.cs in Solution Explorer.

  2. Drag the activity CurrentRequestActivity from the FIM Activities tab of the Toolbox into the workflow designer sheet.

  3. Drag and drop a Code activity from the Toolbox into the workflow designer sheet right below the previous activity. This activity is located under the Windows Workflow v3.0 tab in the toolbox.

    The design view of the RequestLoggingActivity should look like the following illustration.

    The Design view of the activity.

  4. Right-click the activity that you just added (currentRequestActivity1), and then click Properties.

  5. In the Properties window, change the activity name to ReadCurrentRequestActivity. This activity will be used to read information about the current activity.

  6. Bind the CurrentRequest dependency property that you created in RequestLoggingActivity.cs to the CurrentRequest property of the ReadCurrentRequestActivity activity. This property stores request data that was retrieved by the ReadCurrentRequestActivity activity.

    • Select the ReadCurrentRequestActivity activity in the workflow designer sheet. The properties window should look like the following illustration.

      CurrentRequest properties.

    • Click the ellipses (…) next to the CurrentRequest property. This action opens a dialog box that can bind the CurrentRequest property to a variable in our code. Because you already created the property that you need, select the Bind to an existing member tab.

      Binding an existing member to the property.

    Click OK.

  7. Rename the Code activity to LogRequestDataToFile:

    • Right-click the activity Code that you just added (codeActivity1), and then click Properties.

    • In the properties window, change the activity name to LogRequestDataToFile. This activity will be used to log information about the request to a file.

You should now see the following sequence of activities in the workflow designer sheet.

The Design view of the activity.

Notice on the workflow designer sheet the red circle with an exclamation mark next to the Code activity LogRequestDataToFile. This means that the code activity has not been defined yet.

In the following procedure, you add code that writes data about the current request to a file.

To define the code for the code activity

  1. Double-click the Code activity LogRequestDataToFile to generate the following code for it in the file RequestLoggingActivity.cs:

    private void LogRequestDataToFile_ExecuteCode(object sender, EventArgs e)
    {
    }
    
  2. Replace the previous three lines of code with the following:

    #region Execution Logic
    
    /// <summary>
    ///  Defines the logic of the LogRequestDataToFile activity.
    ///  This code will be executed when the LogRequestDataToFile activity
    ///  becomes the active workflow.
    /// </summary>
    private void LogRequestDataToFile_ExecuteCode(object sender, EventArgs e)
    {
         try
         {
              //Get current request from previous activity
              RequestType currentRequest = this.ReadCurrentRequestActivity_CurrentRequest;
    
              // Output the Request type and object type
              this.Log("Request Operation: " + currentRequest.Operation);
              this.Log("Target Object Type: " + currentRequest.TargetObjectType);
    
              // As UpdateRequestParameter derives from CreateRequestParameter we can simplify the code by deriving
              // from CreateRequestParameter only.
              ReadOnlyCollection<CreateRequestParameter> requestParameters = currentRequest.ParseParameters<CreateRequestParameter>();
    
              // Loop through CreateRequestParameters and print out each attribute/value pair
              this.Log("Parameters for request: " + currentRequest.ObjectID);
              foreach (CreateRequestParameter requestParameter in requestParameters)
              {
                   if (requestParameter.Value != null)
                        this.Log("     " + requestParameter.PropertyName + ": " + requestParameter.Value.ToString());
              }
    
              // In order to read the Workflow Dictionary we need to get the containing (parent) workflow
              SequentialWorkflow containingWorkflow = null;
              if (!SequentialWorkflow.TryGetContainingWorkflow(this, out containingWorkflow))
              {
                   throw new InvalidOperationException("Unable to get Containing Workflow");
              }
              this.Log("Containing Workflow Dictionary (WorkflowData):");
    
              // Loop through Workflow Dictionary and log each attribute/value pair
              foreach (KeyValuePair<string, object> item in containingWorkflow.WorkflowDictionary)
              {
                   this.Log("     " + item.Key + ": " + item.Value.ToString());
              }
                   this.Log("\n\n");
         }
         catch (Exception ex)
         {
              this.Log("Logging Activity Exception Thrown: " + ex.Message);
         }
    }
    #endregion
    

    Note that to get information about the workflow dictionary, the code activity gets the containing workflow for this activity, which is the FIM parent workflow. The workflow dictionary can be used to share data between activities within the same parent workflow.

  3. Add the following helper function, named Log, after the code that you just added.

    #region Utility Functions
    
    // Prefix the current time to the message and log the message to the log file.
    private void Log(string message)
         {
              using (StreamWriter log = new StreamWriter(Path.Combine(this.LogFilePath, this.LogFileName), true))
              {
                   log.WriteLine(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + ": " + message);
                   //since the previous line is part of a "using" block, the file will automatically
                   //be closed (even if writing to the file caused an exception to be thrown).
                   //For more information see
                   // https://msdn.microsoft.com/en-us/library/yh598w02.aspx
              }
         }
    #endregion
    
  4. Right-click RequestLoggingActivity.cs in Solution Explorer, and then click View Designer. Verify that the error indicator for the Code activity disappeared. The workflow designer sheet should look like the following illustration.

    Design view after ExecuteCode implemented.

The ExecuteCode event handler has now been defined. When the LogRequestDataToFile code activity becomes the active activity within the workflow, the code in the ExecuteCode event handler is executed.

You have now created a custom activity that logs data about the current request to a file.

Creating a User Interface for the Activity

In the previous exercise, you saw how to create a custom activity in Visual Studio. Now, you are going to develop a UI for your custom activity.

For the custom activity to be rendered in the Workflow Designer of the FIM Portal, the web control must implement the IActivitySettingsPart interface, which is defined in the Microsoft.IdentityManagement.WFExtensionInterfaces assembly that FIM installs. To create this UI, you create a class, called RequestLoggingActivitySettingsPart, that derives from the FIM base class ActivitySettingsPart. This class is defined in the namespace Microsoft.IdentityManagement.WebUI.Controls, and it is used by the FIM Portal to render the UI for any activity. In the derived class RequestLoggingActivitySettingsPart, you are going to override a number of methods from the base class.

When the custom activity is rendered, it will have three main areas:

  1. Title: When the custom activity is rendered in the Workflow Designer, the title bar of the rendered activity is set by the value of the abstract Title property that is defined in ActivitySettingsPart.

  2. Body: Contains web controls that are derived from ActivitySettingsPart. The main area of the rendered custom activity consists of web controls that you added into that control.

  3. Action buttons

    1. Save: Clicking this button calls the ValidateInputs method first. If the method returns True, the GenerateActivityOnWorkflow method is called to serialize and save the activity. Then, SwitchMode is called to make the control read-only.

    2. Cancel: Clicking this button calls the LoadActivitySettings method to load the original data. Then, the SwitchMode method is called to make the control read-only.

The assemblies that Microsoft Windows SharePoint Services uses to render the user interface for FIM are placed in the Global Catalog Cache (GAC). As a result they cannot be referenced directly by Visual Studio. Complete the following procedure to obtain copies of these files that Visual Studio can reference.

If you already have a copy of the Microsoft.IdentityManagement.WebUI.Controls.dll Microsoft.IdentityManagement.WFExtensionInterfaces.dll assemblies, you can skip the following procedure.

To create FIM UI assembly files that Visual Studio can reference

  1. Browse to the following folder: C:\Program Files\Microsoft Forefront Identity Manager\2010\Portal

  2. Create a copy of the MicrosoftILMPortalCommonDlls.wsp file, and rename it to MicrosoftILMPortalCommonDlls.cab.

  3. Right-click MicrosoftILMPortalCommonDlls.cab, and then click Explore. If this does not work, double click MicrosoftILMPortalCommonDlls.cab to see the list of files.

  4. Copy the following two files to the C:\Program Files\Microsoft Forefront Identity Manager\2010\Portal folder:

    • Microsoft.IdentityManagement.WebUI.Controls.dll

    • Microsoft.IdentityManagement.WFExtensionInterfaces.dll

You now have copies of the two assemblies that you need to create a UI for your activity. In the following procedure, you add references to those assemblies to your project.

Now, you add references to the FIM assemblies that are used to define the activity UI.

To add references to FIM UI assemblies

  1. In Solution Explorer, right-click the References folder, and then click Add Reference.

  2. In the Add Reference dialog box, click the Browse tab, and browse to folder that contains the Microsoft.IdentityManagement.WebUI.Controls.dll and Microsoft.IdentityManagement.WFExtensionInterfaces.dll assemblies.

    Tip

    By default, these assemblies are not installed in a location that can be referenced directly by Visual Studio. Follow the steps in the previous procedure to obtain these files.

  3. Select these assemblies, and click OK.

To create a UI for the activity

  1. In Solution Explorer, right-click the LoggingActivitiesLibrary project, click Add, and then click Class.

  2. Select the Class template, and name the class RequestLoggingActivitySettingsPart.cs. Click Add.

  3. Overwrite the code that is automatically generated for the class with the following text:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web.UI.WebControls;
    using System.Workflow.ComponentModel;
    using Microsoft.IdentityManagement.WebUI.Controls;
    using Microsoft.ResourceManagement.Workflow.Activities;
    using FIM.CustomWorkflowActivitiesLibrary.Activities;
    
    namespace FIM.CustomWorkflowActivitiesLibrary.WebUIs
    {
        class RequestLoggingActivitySettingsPart : ActivitySettingsPart
        {
        }
    }
    
  4. Because this class must implement the ActivitySettingsPart base class, you are going to have Visual Studio generate the implementation of this class. Right-click ActivitySettingsPart in the code, and then select Implement Abstract Class, as shown in the following illustration.

    Implementing the abstract class.

    The RequestLoggingActivitySettingsPart class should now contain the following code:

    class RequestLoggingActivitySettingsPart : ActivitySettingsPart
    {
        public override Activity GenerateActivityOnWorkflow(SequentialWorkflow workflow)
        {
            throw new NotImplementedException();
        }
    
        public override void LoadActivitySettings(Activity activity)
        {
            throw new NotImplementedException();
        }
    
        public override ActivitySettingsPartData PersistSettings()
        {
            throw new NotImplementedException();
        }
    
        public override void RestoreSettings(ActivitySettingsPartData data)
        {
            throw new NotImplementedException();
        }
    
        public override void SwitchMode(ActivitySettingsPartMode mode)
        {
            throw new NotImplementedException();
        }
    
        public override string Title
        {
            get { throw new NotImplementedException(); }
        }
    
        public override bool ValidateInputs()
        {
            throw new NotImplementedException();
        }
    }
    
  5. Add a method called CreateChildControls, along with its helper methods. These new methods put web controls that you define for your activity onto the design surface of the FIM UI. In the CreateChildControls method, you create a layout table with two rows. Each row contains a text box and label that correspond to the log file name and log file path for the log that is associated with your activity. Add the following methods to the class after the ValidateInputs method:

    /// <summary>
    ///  Creates a Table that contains the controls used by the activity UI
    ///  in the Workflow Designer of the FIM portal. Adds that Table to the
    ///  collection of Controls that defines each activity that can be selected
    ///  in the Workflow Designer of the FIM Portal. Calls the base class of 
    ///  ActivitySettingsPart to render the controls in the UI.
    /// </summary>
    protected override void CreateChildControls()
    {
        Table controlLayoutTable;
        controlLayoutTable = new Table();
    
        //Width is set to 100% of the control size
        controlLayoutTable.Width = Unit.Percentage(100.0); 
        controlLayoutTable.BorderWidth = 0;
        controlLayoutTable.CellPadding = 2;
        //Add a TableRow for each textbox in the UI 
        controlLayoutTable.Rows.Add(this.AddTableRowTextBox("Log File Path:", "txtLogFilePath", 400, 100, false, "Enter the log file Path."));
        controlLayoutTable.Rows.Add(this.AddTableRowTextBox("Log File Name:", "txtLogFileName", 400, 100, false, "Enter the log file Name."));
        this.Controls.Add(controlLayoutTable);
    
        base.CreateChildControls();
    }
    
    #region Utility Functions
    //Create a TableRow that contains a label and a textbox.
    private TableRow AddTableRowTextBox(String labelText, String controlID, int width, int
                                         maxLength, Boolean multiLine, String defaultValue)
    {
        TableRow row = new TableRow();
        TableCell labelCell = new TableCell();
        TableCell controlCell = new TableCell();
        Label oLabel = new Label();
        TextBox oText = new TextBox();
    
        oLabel.Text = labelText;
        oLabel.CssClass = base.LabelCssClass;
        labelCell.Controls.Add(oLabel);
        oText.ID = controlID;
        oText.CssClass = base.TextBoxCssClass;
        oText.Text = defaultValue;
        oText.MaxLength = maxLength;
        oText.Width = width;
        if (multiLine)
        {
            oText.TextMode = TextBoxMode.MultiLine;
            oText.Rows = System.Math.Min(6, (maxLength + 60) / 60);
            oText.Wrap = true;
        }
        controlCell.Controls.Add(oText);
        row.Cells.Add(labelCell);
        row.Cells.Add(controlCell);
        return row;
    }
    
    string GetText(string textBoxID)
    {
        TextBox textBox = (TextBox)this.FindControl(textBoxID);
        return textBox.Text ?? String.Empty;
    }
    void SetText(string textBoxID, string text)
    {
        TextBox textBox = (TextBox)this.FindControl(textBoxID);
        if (textBox != null)
            textBox.Text = text;
        else
            textBox.Text = "";
    }
    
    //Set the text box to read mode or read/write mode
    void SetTextBoxReadOnlyOption(string textBoxID, bool readOnly)
    {
        TextBox textBox = (TextBox)this.FindControl(textBoxID);
        textBox.ReadOnly = readOnly;
    }
    #endregion
    
  6. Replace the GenerateActivityOnWorkflow method with the following code:

    /// <summary>
    /// Called when a user clicks the Save button in the Workflow Designer. 
    /// Returns an instance of the RequestLoggingActivity class that 
    /// has its properties set to the values entered into the text box controls
    /// used in the UI of the activity. 
    /// </summary>
    public override Activity GenerateActivityOnWorkflow(SequentialWorkflow workflow)
    {
        if (!this.ValidateInputs())
        {
            return null;
        }
        RequestLoggingActivity LoggingActivity = new RequestLoggingActivity();
        LoggingActivity.LogFilePath = this.GetText("txtLogFilePath");
        LoggingActivity.LogFileName = this.GetText("txtLogFileName");
        return LoggingActivity;
    }
    
  7. Replace the PersistSettings method with the following code:

    /// <summary>
    /// Saves the activity settings.
    /// </summary>
    public override ActivitySettingsPartData PersistSettings()
    {
        ActivitySettingsPartData data = new ActivitySettingsPartData();
        data["LogFilePath"] = this.GetText("txtLogFilePath");
        data["LogFileName"] = this.GetText("txtLogFileName");
        return data;
    }
    
  8. Replace the LoadActivitySettings method with the following code:

    /// <summary>
    /// Called by FIM when the UI for the activity must be reloaded.
    /// It passes us an instance of our workflow activity so that we can
    /// extract the values of the properties to display in the UI.
    /// </summary>
    public override void LoadActivitySettings(Activity activity)
    {
        RequestLoggingActivity LoggingActivity = activity as RequestLoggingActivity;
        if (null != LoggingActivity)
        {
            this.SetText("txtLogFilePath", LoggingActivity.LogFilePath);
            this.SetText("txtLogFileName", LoggingActivity.LogFileName);
        }
    }
    
  9. Replace the RestoreSettings method with the following code:

    /// <summary>
    ///  Restores the activity settings in the UI
    /// </summary>
    public override void RestoreSettings(ActivitySettingsPartData data)
    {
        if (null != data)
        {
            this.SetText("txtLogFilePath", (string)data["LogFilePath"]);
            this.SetText("txtLogFileName", (string)data["LogFileName"]);
        }
    }
    
  10. Replace the SwitchMode method with the following code:

    /// <summary>
    ///  Switches the activity between read only and read/write mode
    /// </summary>
    public override void SwitchMode(ActivitySettingsPartMode mode)
    {
        bool readOnly = (mode == ActivitySettingsPartMode.View);
        this.SetTextBoxReadOnlyOption("txtLogFilePath", readOnly);
        this.SetTextBoxReadOnlyOption("txtLogFileName", readOnly);
    }
    
  11. Replace the Title method with the following code:

    /// <summary>
    ///  Returns the activity name.
    /// </summary>
    public override string Title
    {
        get { return "Request Logging Activity"; }
    }
    
  12. Replace the ValidateInputs method with the following code:

    /// <summary>
    ///  In general, this method should be used to validate information entered
    ///  by the user when the activity is added to a workflow in the Workflow
    ///  Designer.
    ///  We could add code to verify that the log file path already exists on
    ///  the server that is hosting the FIM Portal and check that the activity
    ///  has permission to write to that location. However, the code
    ///  would only check if the log file path exists when the
    ///  activity is added to a workflow in the workflow designer. This class
    ///  will not be used when the activity is actually run.
    ///  For this activity we will just return true.
    /// </summary>
    public override bool ValidateInputs()
    {
        return true;
    }
    
  13. Save the project: On the File menu, click Save All.

  14. Build the project: On the Build menu, click Solution (or press F6).

You now have a class that defines the UI for the activity in the Workflow Designer of the FIM Portal.

Building the Assembly and Loading it into the FIM Portal

Note

Custom activities must be signed with a strong name so that they can be put in the Global Assembly Cache (GAC) of the FIM server. Only custom activities that are in the GAC will load correctly and be displayed in the Workflow Designer in the FIM Portal. See How to: Sign an Assembly with a Strong Name.

The following procedure shows how to sign the assembly with a strong name and build the assembly that contains the activity.

To sign the assembly with a strong name

  1. In Solution Explorer, right-click the Properties folder, and then click Open.

    This action opens a project properties file in the workspace that has the same name as your project. In this example, the tab is named LoggingActivitiesLibrary.

  2. Click the Signing tab, and then select the Sign the assembly check box. Select a strong name key file from the drop-down list, or select <New>, and then create a new key file name, such as FIM.CustomActivityWorkflowsLibrary.

  3. Clear the Protect my key file with a password check box.

    Note

    You can choose to protect your key file with a password. For more information, see Create Strong Name Key Dialog Box.

  4. Save the project: On the File menu, click Save All.

  5. Build the project: On the Build menu, click Solution (or press F6).

You should now have an assembly named LoggingActivityLibrary.dll that can be copied to the GAC of the FIM server.

The following procedure shows how to load the LoggingActivityLibrary into the GAC of the FIM server, start and stop the FIM service, and run IISRESET. You must follow these steps to use the activity in the FIM Portal.

Tip

You can add a post-build event to automate some of these steps. For more information, see Automating Deployment of Activity Changes to FIM.

To load the LoggingActivityLibrary assembly into FIM

  1. Using Windows Explorer, browse to the LoggingActivityLibrary folder that stores your project. Locate the LoggingActivitiesLibrary.dll assembly that you created when you built your project. For this example, this file should be located in a folder that is named LoggingActivitiesLibrary\bin\Debug. Copy that file to the server that is hosting the FIM Service. In this example we copied the file to C:\Temp.

  2. On the Start menu on the server, click All Programs, and then click Accessories.

  3. Right-click Command Prompt, and then click Run as administrator.

  4. Type the following command at the command prompt, and then press ENTER:

    "C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\gacutil" /i C:\Temp\LoggingActivitiesLibrary.dll
    
  5. Using Windows Explorer, browse to the GAC folder C:\Windows\Assembly.

  6. Right-click LoggingActivitiesLibrary in the GAC, and then click Properties. You should see a dialog box similar to the following illustration.

    View activity properties in the GAC.

  7. Note the Public Key Token, Version, and Culture of the assembly. You will need that information to configure the activity in FIM. Note that your public key is different from the public key that is displayed in the previous illustration.

  8. Type the following command at the command prompt, and then press ENTER:

    net stop "Forefront Identity Manager Service"
    

    Wait until a message appears at the command line that indicates the service has been stopped. This message should resemble the following:

    “The Forefront Identity Manager Service service was stopped successfully.”
  9. Type the following command at the command prompt, and then press ENTER:

    net start "Forefront Identity Manager Service"
    

    Wait for a message at the command line that indicates the service has been started. This message should resemble the following:

    “The Forefront Identity Manager Service service was started successfully.”
  10. Type the following command at the command prompt, and then press ENTER:

    IISRESET
    

    Wait for a message at the command line that indicates that the IISRESET command has completed. This message should resemble the following:

    “Internet services successfully restarted.”

Activities in the LoggingActivityLibrary assembly can now be configured in FIM.

Note

Every time that you change the assembly, you must add the new version of the assembly to the GAC and then stop and start the FIM Service. If you made changes to the UI of the activity, you must also run IISRESET.

Tip

Instead of using a command prompt, you can start and stop services by using the Services tool. To open Services, click Start, click All Programs, click Administrative Tools, and then click Services. To start or stop a service, right-click the service, and then click Start or Stop.

Configuring the Activity in FIM

Now, you must create an Activity Information Configuration resource in FIM that references your activity. If you configure this resource correctly, your activity will appear in Workflow Designer in the FIM Portal.

Note

To create Activity Information Configuration resources, you may have to modify the out-of-box management policy rule (MPR) named Administration: Administrators control configuration related resources and change the target attributes to All Attributes. This change grants the administrators permission to set any kind of attribute in any configuration resource. If you do not make this change, you might receive a permissions error when you try to save the new Activity Information Configuration resource.

To create an Activity Information Configuration resource for the activity

  1. Open the FIM Portal.

  2. Click Administration, click All Resources, and then click Activity Information Configuration.

  3. Click New.

  4. On the Common Attributes tab, enter values for each of the following attributes, as shown in the following table.

    Attribute Name Value

    Description

    Activity to log information about the current request

    Display Name

    Request Logging Activity

  5. Click Next.

  6. On the Extended Attributes tab, enter values for the following attributes, as shown in the following table.

    Attribute Name Value Notes

    Activity Name

    FIM.CustomWorkflowActivitiesLibrary.Activities.RequestLoggingActivity

    Must match the activity name in our project (including namespace).

    Assembly Name

    LoggingActivitiesLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxx

    The Version, Culture, and PublicKeyValues must match the values that are stored in the GAC. You located those values in a previous section of this document.

    Type Name

    FIM.CustomWorkflowActivitiesLibrary.WebUIs.RequestLoggingActivitySettingsPart

    Must match the name of the class (including namespace) in your project that implements the IActivitySettingsPart interface.

    Is Action Activity

    Checked

    Indicates that the activity can be used in an action workflow.

    Is Authentication Activity

    Checked

    Indicates that the activity can be used in an authentication workflow

    Is Authorization Activity

    Checked

    Indicates that the activity can be used in an authorization workflow

  7. Click Submit.

  8. Verify that the new Activity Information Configuration Resource Request Logging Activity appears on the list of Activity Information Configuration resources.

  9. Type the following command at the command prompt, and then press ENTER:

    IISRESET
    

    Wait for a message at the command line that indicates that the IISRESET command has completed. This message should resemble the following:

    “Internet services successfully restarted.”

Running the Custom Activity

To test the execution of the activity, you must create a workflow that includes the activity, create an MPR that includes that workflow, perform an action that will cause the MPR to be run, and check that the appropriate text was added to the log file.

Creating a Workflow that Includes the Custom Activity

The following procedure shows how to create a workflow that includes the custom activity.

To create a workflow that includes the custom activity

  1. Create a folder on the server that hosts the FIM Service to store logging information. The folder that was entered as the Log File Path value of the activity must already exist for the activity to run without errors.

  2. In the FIM Portal, click the Workflows link.

  3. Click New.

  4. On the Basic Information tab, enter values for the following attributes, as shown in the following table.

    Attribute Name Value

    Workflow Name

    Test Request Logging Activity

    Description

    Logs information about the current request.

    Workflow Type

    Action

  5. Click Next. The Activity Picker should appear in the dialog box, and include the Request Logging Activity activity as shown in the following illustration.

    The Request Logging Activity is displayed.

  6. On the Activities tab, click Request Logging Activity, and then click Select.

  7. Enter the desired logging folder and file name in the UI of the activity. The logging folder must already exist. The dialog box resembles the following illustration.

    Configuring activity properties.

  8. Click Save. The dialog box resembles the following illustration.

    Activity has been added to the workflow.

  9. Click Finish.

  10. Click Submit.

  11. Verify that the new workflow Test Request Logging Activity appears in the list of Workflow resources.

You now have a workflow that contains your Request Logging Activity. This workflow can now be attached to MPRs.

Creating a Management Policy Rule to Run the Workflow

Next, you create an MPR that runs the action workflow that you just created. The MPR that you create will be run when an administrator changes a user's telephone number.

To create an MPR to run the workflow

  1. From the FIM Portal, click the Management Policies link.

  2. Click New.

  3. On the General Information tab, enter values for the following attributes based on the following table.

    Attribute Name Value

    Display Name

    Test Request Logging Activity

    Type

    Request

  4. Click Next.

  5. On the Requestors and Operations tab, enter values for the following attributes based on the following table.

    Attribute Name Value

    Requestors

    Specific Set of Requestors: Administrators

    Operations

    Modify a single-valued attribute

    Grants permissions

    (Not selected)

  6. Click the Validate Inputs button to the right of the Requestors text box.

  7. Click Next.

  8. On the Target Resources tab, enter values for the following attributes based on the following table.

    Attribute Name Value

    Target Resource Definition Before Request

    All People

    Target Resource Definition After Request

    All People

    Resource Attributes

    Office Phone; Mobile Phone;

  9. Click the Validate Inputs button next to the right of each text box.

  10. Click Next.

  11. On the Policy Workflows tab, enter values for the following attributes based on the following table.

    Attribute Name Value

    Authentication

    (none selected)

    Authorization

    (none selected)

    Action

    Test Request Logging Activity

  12. Click Finish. The Summary screen should resemble the following illustration.

    Summary of the new management policy rule.

  13. Click Submit.

Testing the Custom Activity

Now, you make a change in the FIM Portal that will cause the MPR that you created to be run. Then, you check that the appropriate text was added to your log file.

To test the activity

  1. In the FIM Portal, select a user who can be modified for testing or create a new user.

  2. Modify the user's Office Phone and Mobile Phone values. Submit your changes.

  3. Verify that the MPR was run and that the workflow Test Request Logging Activity was run by clicking the Search Request link in the FIM Portal. Look for the log record Update to Person: X Request, in which X represents the user that you modified. View the request details by double-clicking the request. Make sure that the Status of the request is set to Completed.

    Warning

    If your workflow returns an exception, the processing of your request stops. The request's Status attribute then reports an error and the request's RequestStausDetail attribute reports the exception message.

  4. Verify that the log file contains information that is similar to the following:

    Request Operation: Put
    Target Object Type: Person
    Parameters for request: urn:uuid:12690efa-064e-4bc8-92f4-8d8eecc6966e
         MobilePhone: 555-555-0155
         OfficePhone: 555-555-0100
    Containing Workflow Dictionary (WorkflowData):
    

    If your activity is used in a workflow that stores information in a workflow dictionary, each attribute/value pair that is stored in the workflow dictionary is added to the log. You can use a workflow dictionary to share information between activities in the same FIM workflow.

When you updated a user's MobilePhone or OfficePhone value, the workflow logged the request to the log file. You now have an activity that logs data about a current request to a file.

Warning

Remember that the activity that you created will attempt to keep logging information to the log file every time that the associated MPR is run. Writing this information to a file will slow down the FIM Service and keep increasing the size of the log file. Consider using this activity for debugging or for critical events that do not occur often.

security Security Note:
You should consider the security implications of storing information on the server that is hosting the FIM Service. Request information stored in the log file will be available to all users that can access that log file, even if did not have permission to access information about the request in the FIM Portal.

For more information about debugging activities, see Debugging Custom Activities.

Automating Deployment of Activity Changes to FIM

Every time that you make changes to the activity code and you recompile it, you must copy the new assembly to the GAC and restart the FIM Service. You can configure a post-build event in Visual Studio to perform these steps automatically every time you compile the activity successfully.

Warning

The following procedure assumes that you are developing the activity on the same computer that is hosting the FIM Service.

To add a post-build event to the project

  1. Open the project in Visual Studio.

  2. In Solution Explorer, right-click the Properties folder, and then click Open. This action opens a project properties file in the workspace that has the same name as your project. In this example, the file is named LoggingActivitiesLibrary.

  3. Click the Build Events tab.

  4. In Run the post-build event, click On successful build.

  5. Type the following commands in the Post-build event command line text box:

    "C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\gacutil.exe" /i "$(TargetPath)"
    net stop "Forefront Identity Manager Service"
    net start "Forefront Identity Manager Service"
    

    The dialog box should resemble the following illustration.

    Adding Post-build events.

Now, when you build the project successfully, the assembly is copied automatically to the GAC and the FIM Service is restarted. Note that you still have to run IISRESET if you have made changes to the UI of the activity. If you changed the version number of the project, you must also update the Activity Information Configuration resource that is associated with the activity.

Remarks

An administrator must deploy custom activity assemblies to all computers that will create custom workflows that use the custom activities. Each assembly must have a strong name so that it can be added to the GAC of each computer on which it is deployed.

You can build the LoggingActivitiesLibrary project into an assembly or add the code files from that project (RequestLoggingActivity.cs, RequestLoggingActivity.Designer.cs and RequestLoggingActivitySettingsPart.cs) into another library, such as the one created in How to: Create a Custom Activity Library. The namespaces that you used in this example are the same that are used in that library. In general, it may be easier to put all custom activities into one library.

See Also

Concepts

Custom Activities and Workflows
Developing Custom Activities and Workflows
Debugging Custom Activities