Introduction


As everybody knows FIM has great capabilities in user self-services, but when features for helpdesk are required it sometimes gets a little bit tricky. The solution I describe here is a request of a customer who wants to remove an old helpdesk tool for user, group and team management.
The old helpdesk management tool was a web application and worked directly on Active Directory. While I have implemented most of the functionality of the old application there was one problem I've spend a little more time on to find a solution.
This missing feature or problem is that you cannot OOB manage group membership on the user UI in FIM Portal (aka. memberOf Problem).

So after trying and thinking about a lot of possibilities, I've implemented the following solution.

So how does it work?
I've decided to create 2 multi-value reference feeder attributes (one for adding a group, one for removing). In the RCDC I use uocIdentityPicker controls to display them. Adding one or more groups to this attributes will trigger a workflow with the appropriate PowerShell script which adds or removes the user from the groups via the web service. After that the feeder attributes are cleared.
Together with a uocListView control for group reporting on the user UI the look and feel is like adding members to a group in the group UI.



This solution relies on the great FIM PowerShell Activity and FIM PowerShell Module from Craig Martin and Brian Desmond, so make sure you install and configure the following steps correctly.

Prerequisites


Download the following software from codeplex (if you not already using them):
FIM PowerShell Workflow Activity (thanks again Craig and Brian for that.)

FIM PowerShell Module

Make sure you install the FIM PowerShell Module to C:\FIMPowershellModule\ to get the scripts working.
Make sure you use the helper scripts in the PowerShell Workflow Activity package to install the activity in your solution
and to create the FIM Service Account as a person account in the portal. Also activating the logging could be useful when solving problems.

Craig has described this very well in his blog:
Install FIM Powershell Workflow Activity

So again: You must have the FIM Service Account as a person object in FIM Portal and the person object must have set the ObjectSID attribute.

This solution also uses the FIMAutomation PowerShell Cmdlets which are installed as part of the FIM deployment:

Ok then, let’s do it.

Create Attributes and Bindings


1. Go to Administration -> Schema Management -> All Attributes
2. Create first new attribute:
a. SystemName: MemberAddFeeder
b. DisplayName: Add to group
c. Type: Reference (multi-value)

3. Create second new attribute:
a. SystemName: MemberRemoveFeeder
b. DisplayName: Remove from group
c. Type: Reference (multi-value)

4. Go to “All bindings”.
5. Create a binding for the two new attributes to the person object type.

Modify the Resource Control Display Configuration XML file


1. Go to Administration -> RCDCs
2. Open: “Configuration for User Editing” RCDC and export the current XML to a file.
3. Place the following code between two tabs of your choice:
<my:Grouping my:Name="GroupMembership" my:Caption="memberOf" my:Enabled="true" my:Visible="true">
  <my:Control my:Name="GroupList" my:TypeName="UocListView" my:ExpandArea="false" my:Caption="Current Group Memberships">
    <my:Properties>
      <my:Property my:Name="ColumnsToDisplay" my:Value="DisplayName,Description" />
      <my:Property my:Name="EmptyResultText" my:Value="User does not belong to any group" />
      <my:Property my:Name="PageSize" my:Value="10" />
      <my:Property my:Name="ShowTitleBar" my:Value="false" />
      <my:Property my:Name="ShowActionBar" my:Value="false" />
      <my:Property my:Name="ShowPreview" my:Value="false" />
      <my:Property my:Name="ShowSearchControl" my:Value="false" />
      <my:Property my:Name="EnableSelection" my:Value="false" />
      <my:Property my:Name="SingleSelection" my:Value="false" />
      <my:Property my:Name="ListFilter" my:Value="/Group[(ComputedMember='%ObjectID%') or (ExplicitMember='%ObjectID%')]" />
    </my:Properties>
  </my:Control>
  <my:Control my:Name="MemberRemove" my:TypeName="UocIdentityPicker" my:Caption="{Binding Source=schema, Path=MemberRemoveFeeder.DisplayName}" my:Description="{Binding Source=schema, Path=MemberRemoveFeeder.Description}" my:RightsLevel="{Binding Source=rights, Path=MemberRemoveFeeder}">
    <my:Properties>
      <my:Property my:Name="ObjectTypes" my:Value="Group" />
      <my:Property my:Name="ResultObjectType" my:Value="Group" />
      <my:Property my:Name="Value" my:Value="{Binding Source=object, Path=MemberRemoveFeeder, Mode=TwoWay}" />
      <my:Property my:Name="Mode" my:Value="MultipleResult" />
      <my:Property my:Name="Filter" my:Value="/Group[(ExplicitMember='%ObjectID%')]" />
      <my:Property my:Name="ListViewTitle" my:Value="Select one or more group to remove." />
    </my:Properties>
  </my:Control>
  <my:Control my:Name="MemberAdd" my:TypeName="UocIdentityPicker" my:Caption="{Binding Source=schema, Path=MemberAddFeeder.DisplayName}" my:Description="{Binding Source=schema, Path=MemberAddFeeder.Description}" my:RightsLevel="{Binding Source=rights, Path=MemberAddFeeder}">
    <my:Properties>
      <my:Property my:Name="ObjectTypes" my:Value="Group" />
      <my:Property my:Name="ResultObjectType" my:Value="Group" />
      <my:Property my:Name="Value" my:Value="{Binding Source=object, Path=MemberAddFeeder, Mode=TwoWay}" />
      <my:Property my:Name="Mode" my:Value="MultipleResult" />
      <my:Property my:Name="UsageKeywords" my:Value="Group" />
      <my:Property my:Name="ListViewTitle" my:Value="Select one or more group to add." />
    </my:Properties>
  </my:Control>
</my:Grouping>
   
4. Put the XML files back in the “Configuration for User Editing” RCDC.
5. Don’t forget to do an iisreset.

Within the uocIdentityPicker control you have 3 different properties that control which groups are presented in the “Browse...” window or to control the search and resolve behavior. You cannot combine these, instead use one of them:
1. UsageKeywords: Displays all search scope with the given usage keyword in the windows to select group. You are then able to search for groups even with custom filtering.
2. DefaultSearchScopeName: Specify exactly the search scope to use in the select group window. You are also able to do a custom search for groups with this property. This is the only way to search and resolve in integer attributes, also described in this forum post: http://social.technet.microsoft.com/Forums/en-US/79a70b41-898a-41e6-b966-1eeeb08e9a35/attributes-to-search-and-resolving#8a2d769c-143c-4388-a2db-ac94096f0cfb
3. Filter: Define an exclusively to use XPath filter for groups to display in the select group windows. You are not able to search for groups in the select group windows, only the group matching this filter will be displayed. (Use this for small amount of groups only).
Some notes on how I used the properties in this solution.
In the uocIdentityPicker of the MemberRemoveFeeder attribute I use the “Filter” property for only displaying groups to which the user belongs to as an ExplicitMember, so you cannot remove the user from dynamic groups. In the uocIdentityPicker of the MemberAddFeeder attribute I use the “UsageKeywords” property to display groups based on an search scope (all groups in this case), if you use another search scope and maybe have only a small amount of groups (around for ex. 20) you can try to also use the “Filter” property on this.

Create the sets


1. Create a set for the FIM service account person object:
a. DisplayName: FIM service account
b. Criteria: User the match All criteria, ResourceID is “FIM service account person object”

2. Create a set for the helpdesk users:
a. DisplayName: Helpdesk Users
b. Criteria: Built criteria that matches your helpdesk users, make sure the FIM service account does not belong to this set (Important!!!).

Create the workflows


1. Create an action workflow with the following parameters:
a. DisplayName: UserUI - add member to group and clear feeder attribute
b. Add the Powershell activity to the workflow with the following script:
if (-not $fimwf)
{
    Throw "Failed to get workflow details from the FIM Request"
}
Write-Verbose "Processing FIM WF with Request Details: $fimwf"
 
# Load Microsoft FIMAutomation SnapIn and PowerShell Modules from Craig Martin
Write-Verbose "Loading SnapIns and Modules"
Add-PSSnapin FIMAutomation
Import-Module C:\FIMPowershellModule\FimPowerShellModule.psm1 -Verbose:$false
 
# Function to get Member AccountName from the Request Details
function GetObjectAccountName($ReqObject, $guid)
{
    $SearchGuid = "urn:uuid:" + $guid
    $Object = $ReqObject | where { $_.ObjectId -eq $SearchGuid }
    $Object.AccountName
}
 
# Get Request from FIM-Service
Write-Verbose "Getting the request object from FIMService"
$Request = Export-FIMConfig -Custom ("/*[ObjectID='{0}']" -F $fimwf.RequestId.Guid) | Convert-FimExportToPSObject
 
# Useful Information is found in the RequestParameter XML data
# The first object in the array is always the request.
$ReqParmList=$Request[0].RequestParameter
$Username=GetObjectAccountName $Request $fimwf.TargetId.Guid
 
# Process each of the member changes
Write-Verbose "Processing application changes for this user"
foreach ($ReqParm in $ReqParmList)
{
    $ReqParmXML = [XML]$ReqParm
    if ($ReqParmXML.RequestParameter.PropertyName -like "MemberAdd*" -and $ReqParmXML.RequestParameter.Operation -eq "Create")
    {
        switch ($ReqParmXML.RequestParameter.Mode)
        {
        "Add" {
            $GroupName = GetObjectAccountName $Request $ReqParmXML.RequestParameter.Value."#text"
            New-FimImportObject -ObjectType Group -State Put -Anchor @{AccountName='{0}' -F $GroupName} `
            -Changes @(New-FimImportChange -Operation 'Add' -AttributeName 'ExplicitMember' -AttributeValue ('Person','AccountName',('{0}' -F $UserName))) -ApplyNow
        }
             
        default { Throw "Invalid Mode on UserUI group add" }
        }
    }
}

c. Next add the Function Evaluator activity with the following parameters:
i. DisplayName: Clear MemberAddFeeder attribute
ii. Destination: [//Target/MemberAddFeeder]
iii. Value: String “ “ (must be a space in order to clear reference attributes)

2. Create another action workflow with the following parameters:
a. DisplayName: UserUI - remove member from group and clear feeder attribute
b. Add the Powershell activity to the workflow with the following script:
if (-not $fimwf)
{
    Throw "Failed to get workflow details from the FIM Request"
}
Write-Verbose "Processing FIM WF with Request Details: $fimwf"
 
# Load Microsoft FIMAutomation SnapIn and PowerShell Modules from Craig Martin
Write-Verbose "Loading SnapIns and Modules"
Add-PSSnapin FIMAutomation
Import-Module C:\FIMPowershellModule\FimPowerShellModule.psm1 -Verbose:$false
 
# Function to get Member AccountName from the Request Details
function GetObjectAccountName($ReqObject, $guid)
{
    $SearchGuid = "urn:uuid:" + $guid
    $Object = $ReqObject | where { $_.ObjectId -eq $SearchGuid }
    $Object.AccountName
}
 
# Get Request from FIM-Service
Write-Verbose "Getting the request object from FIMService"
$Request = Export-FIMConfig -Custom ("/*[ObjectID='{0}']" -F $fimwf.RequestId.Guid) | Convert-FimExportToPSObject
 
# Useful Information is found in the RequestParameter XML data
$ReqParmList=$Request[0].RequestParameter
$Username=GetObjectAccountName $Request $fimwf.TargetId.Guid
 
# Process each of the member changes
Write-Verbose "Processing application changes for this user"
foreach ($ReqParm in $ReqParmList)
{
    $ReqParmXML = [XML]$ReqParm
    if ($ReqParmXML.RequestParameter.PropertyName -like "MemberRemove*" -and $ReqParmXML.RequestParameter.Operation -eq "Create")
    {
        switch ($ReqParmXML.RequestParameter.Mode)
        {
        "Add" {
            $GroupName = GetObjectAccountName $Request $ReqParmXML.RequestParameter.Value."#text"
            New-FimImportObject -ObjectType Group -State Put -Anchor @{AccountName='{0}' -F $GroupName} `
            -Changes @(New-FimImportChange -Operation 'Delete' -AttributeName 'ExplicitMember' -AttributeValue ('Person','AccountName',('{0}' -F $UserName))) -ApplyNow
        }
             
        default { Throw "Invalid Mode on application group add" }
        }
    }
}

c. Next add the Function Evaluator activity with the following parameters:
i. DisplayName: Clear MemberRemoveFeeder attribute
ii. Destination: [//Target/MemberRemoveFeeder]
iii. Value: String “ “ (must be a space in order to clear reference attributes)

Create the MPR’s


1. Create a request based MPR for permissions of the FIM service account:
a. Requestor: FIM service account
b. Operation: Add and Remove multi-value attribute
c. Permission: Grants permission
d. Target: All groups
e. Attributes: Manually-managed Membership

2. Create a request based MPR for permission of the helpdesk users:
a. Requestor: Helpdesk Users
b. Operation: Add and Remove multi-value attribute
c. Permission: Grants permission
d. Target: All People
e. Attributes: MemberAddFeeder, MemberRemoveFeeder

3. Create a request based MPR to trigger on group adding:
a. Requestor: Helpdesk Users
b. Operation: Add multi-value attribute
c. Target: All People
d. Attributes: MemberAddFeeder
e. Action Workflow: UserUI - add member to group and clear feeder attribute

4. Create a request based MPR to trigger on group removing:
a. Requestor: Helpdesk Users
b. Operation: Add multi-value attribute
c. Target: All People
d. Attributes: MemberRemoveFeeder
e. Action Workflow: UserUI - remove member from group and clear feeder attribute

Modify default Sets


In my solution the FIM service account is not owner of the group, so the default FIM MPRs and validation workflows deny editing attributes by this account. Because of that I decided to edit a default set in order to allow the FIM service account to do so. Maybe you find a better solution for that on your own.

1. Modify the “All Non-Administrators” set:
a. Add Criteria: ResourceID not in FIM service account
b. Make sure criteria is: Users that match All of the following criteria’s

Testing


So, that’s all. Go to the User UI and add or remove some groups like you would do in Active Directory.

Notes and modifications


In the final solution of my customer I’ve implemented the above solution multiple times. I’ve categorized the group which are imported from active directory based on the organizational unit they are placed.
With the import flows I set an attribute called groupClass to separate the groups in share, application, printer and team groups. Then I am able to show such groups on separated tabs on the user UI and also build the feeder attributes (add,remove) for each groupClass. By setting the appropriate filter and search scopes in the RCDCs this gives a really neat look and feel.

So here is a screenshot from that final solution: