Introduction:

As part of the FIM implementation within my organisation, I needed to replicate the management of automatic security and distribution groups.

Now this is ugly, not nice/ ideal etc. but to get it done – I need to avoid any changes to the Status Quo – my org does not like change! I can work on making things better later in time.

The current process has an interesting concept of what automatic means.

In that, where normally, an attribute e.g. representing the Finance dept. might be used to identify members of a group; the filter identifying members might use organisational attributes; there might also be users who do not fit within that structure who need to be added. Or there might be people who should be within a group because their organisational attributes match the filter, but for whatever reason, they do not want to be a member of the group.

So essentially, the current filter process might provide organisational attributes to populate the group, but there may also be additional explicit includes and excludes. All automatic groups also remove users who have been disabled.

Note that the organisation structure is not hierarchical.

Method:

Using the GUI to define the filters is time consuming, so I took the current filters and translated them into XPATH filters. In fact some of the more complicated filers cannot be rendered by the GUI, but pressing the show members button does return the correct number of users.

An example of a “complicated” filter and the resultant GUI:

/Person[(EmployeeStatus = ‘Enabled’) and (emailAddressPresent= ‘True’) and ((Site = ‘NORTH’) or (Site = ‘SOUTH’)) and (Department = ‘FIN’) and (not(AccountName = ‘User1’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User2’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User3’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User4’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User5’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User6’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User7’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User8’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User9’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User10’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User11’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User12’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User13’))]

ComplicatedFilter

The filter above is placed within the Filter attribute of the group. Accessible via Advanced view, Extended Attributes:


If I break down this filter:

The main “Automatic” bit: (EmployeeStatus = ‘Enabled’) and (emailAddressPresent= ‘True’) and ((Site = ‘NORTH’) or (Site = ‘SOUTH’)) and (Department = ‘FIN’) = Enabled accounts, who have an email address (this is a distribution list), who are on Site NORTH or SOUTH and whose Department attribute is “FIN”  Note that EmployeeStatus, emailAddressPresent and Site are custom attributes within the MV and Portal, that are populated via a classic import rules from HR/AD.

The explicit add and removes: (not(AccountName = ‘User1’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User2’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User3’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User4’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User5’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User6’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User7’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User8’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User9’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User10’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User11’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User12’)) or ((EmployeeStatus = ‘Enabled’) and (AccountName = ‘User13’)) = Remove/ disallow User1 (explicit remove), then add (explicit add) each of those other accounts, only if they are enabled (disabled accounts are removed from groups).

A little note about (emailAddressPresent= ‘True’), this MV attribute is populated on the import from AD. It is defined by the presence of a mail address that matches those that are valid for the organisation and that the persons msExchHomeServerName is present. It is then exported to the Portal with the same name, for use in these filters:

01.Case "emailAddressPresent-ADMA-Import"
02.        'AD attributes required: mail and msExchHomeServerName
03.        If csentry("mail").IsPresent And csentry("msExchHomeServerName").IsPresent Then
04.            Dim mail As String = csentry("mail").Value
05.            Dim suffix() As String = mail.Split("@")
06.            'Valid/allowed email suffixes are defined in the following array (amend as appropriate):
07.            Dim validMailAddresses() As String = {"blah.org", "blah.ac.uk", "blah.org.uk", "laserz.org"}
08.            If (Array.IndexOf(validMailAddresses, suffix(1).ToLower) > -1) Then
09.                mventry("emailAddressPresent").Value = "True"
10.            Else
11.                'If a suffix from the array is not found - raise an error, so that the suffix can be added to the array or simply sorted out - where a mistake was made.
12.                Throw New Exception("Invalid email suffix found: " & suffix(1))
13.            End If
14.        ElseIf Not csentry("msExchHomeServerName").IsPresent Then
15.            mventry("emailAddressPresent").Value = "False"
16.        End If

Each of the XPATH filters are stored in a CSV file with the corresponding Display Name – I’ll use this PS script to import those filters into the respective group: https://social.technet.microsoft.com/Forums/en-US/63dcd915-c92d-4fa2-8183-45e2db601693/using-powershell-to-turn-static-groups-into-dynamic-groups?forum=ilm2, I have a slightly modified version here:

01.if (@(get-pssnapin | where-object {$_.Name -eq "FIMAutomation"} ).count -eq 0)
02.{
03. Add-PSSnapIn FIMAutomation
04.}
05.function GenerateFilter
06.{
07. PARAM ($xpathFilter)
08. END
09. {   
10.  return "<Filter xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`" Dialect=`"http://schemas.microsoft.com/2006/11/XPathFilterDialect`" xmlns=`"http://schemas.xmlsoap.org/ws/2004/09/enumeration`">" + $xpathFilter + "</Filter>"
11. }
12.}
13.function CreateImportChange
14.{
15. PARAM($AttributeName, $AttributeValue, $Operation)
16. END
17. {
18.  $importChange = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportChange
19.  $importChange.Operation = $Operation
20.  $importChange.AttributeName = $AttributeName
21.  $importChange.AttributeValue = $AttributeValue
22.  $importChange.FullyResolved = 1
23.  $importChange.Locale = "Invariant"
24.  return $importChange
25. }
26.}
27.function GetAttributeValueFromResource
28.{
29. PARAM ($exportObject, $attributeName)
30. END
31. {
32.  foreach ($attribute in $exportObject.ResourceManagementObject.ResourceManagementAttributes)
33.  {   
34.   if($attribute.AttributeName.Equals($attributeName))
35.   {
36.    if ($attribute.IsMultiValue)
37.    {
38.     return $attribute.Values
39.    }
40.    else
41.    {
42.     return $attribute.Value
43.    }
44.   }
45.  }
46.  return $null
47. }
48.}
49.$csv = Import-Csv -delimiter `t -header "GroupName","Filter" "C:\FIMScripts\MyFile.csv"
50.foreach ($entry in $csv)
51.{
52. $myGroupName=$entry.GroupName
53. $myFilter = $entry.Filter
54. #Write-Host "Name:" $myGroupName
55. #Write-Host "Filter:" $myFilter
56. $group = Export-FIMConfig -customConfig "/Group[DisplayName='$myGroupName']" -onlyBaseResources
57. if ($group -eq $NULL) #if group doesn't exist, continue
58. {
59. Write-Host "Group does not exist!:" $myGroupName
60.  continue
61. }
62. $filter = GenerateFilter -xpathFilter $myFilter
63. #Write-Host "xpathFilter:" $filter
64. #construct the web service operation
65. $importObject = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportObject
66. #the object type is Group
67. $importObject.ObjectType = "Group"
68. #we are modify the group we've identified above
69. $importObject.SourceObjectIdentifier = $group.ResourceManagementObject.ObjectIdentifier
70. $importObject.TargetObjectIdentifier = $group.ResourceManagementObject.ObjectIdentifier
71. #Put operation is enum 1
72. $importObject.State = 1
73. #construct the operation to Replace filter, Replace attribute operation is enum 1
74. $importObject.Changes += CreateImportChange -attributeName "Filter" -attributeValue $filter -operation 1
75. #construct the operation to change membership add workflow to None. Replace attribute operation is enum 1
76. $importObject.Changes += CreateImportChange -attributeName "MembershipAddWorkflow" -attributeValue "None" -operation 1
77. #construct the operation to change membership locked to True. Replace attribute operation is enum 1
78. $importObject.Changes += CreateImportChange -attributeName "MembershipLocked" -attributeValue "True" -operation 1
79. #construct the operations to remove explicit members. Remove attribute operation is enum 2
80. $explicitMembers = GetAttributeValueFromResource -exportObject $group -attributeName "ExplicitMember"
81. if ($explictMembers -ne $NULL)
82. {
83.  foreach ($explicitMember in $explicitMembers)
84.  {
85.   $importObject.Changes += CreateImportChange -attributeName "ExplicitMember" -attributeValue $explicitMember -Operation 2
86.  }
87. }
88. $importObject | Import-FIMConfig
89. $undone.Count
90.}

So, after importing all of those filters, there is a need to make sure that the members of the FIM groups match those in AD.

To get the group members from AD:

01.Const ADS_SCOPE_SUBTREE = 2
02.Const ForWriting = 2
03.strLDAP="OU=GroupOU,DC=contoso,DC=com"
04.Set objConnection = CreateObject("ADODB.Connection")
05.Set objCommand =   CreateObject("ADODB.Command")
06.objConnection.Provider = "ADsDSOObject"
07.objConnection.Open "Active Directory Provider"
08.Set objCommand.ActiveConnection = objConnection
09.objCommand.Properties("Page Size") = 1000
10.objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
11.objCommand.CommandText = _
12.    "SELECT distinguishedName FROM 'LDAP://" & strLDAP & "' WHERE objectCategory='group'" 
13.Set objRecordSet = objCommand.Execute
14.objRecordSet.MoveFirst
15.Do Until objRecordSet.EOF
16.    strGroupName = objRecordSet.Fields("distinguishedName").Value
17. GetMember(strGroupName)
18.    objRecordSet.MoveNext
19.Loop
20.Function GetMember(strGroupName)
21.Set objGroup = GetObject("LDAP://" & strGroupName)
22.Wscript.Echo "Group Name: " & objGroup.displayName
23.strPath = "C:\PathToOutputFile\"
24.Set objFso = CreateObject("Scripting.FileSystemObject")
25. 'Output filename is the displayname of the group
26. Set objOutputFile = objFSO.OpenTextFile(strPath & objGroup.displayName & ".txt", 2, True)
27. For Each objMember in objGroup.Members
28.'Entry written to file is the samAccountName
29.strCN=objMember.cn
30.objOutputFile.writeline strCN
31.Next
32.objOutputFile.Close
33.End Function

To get the group members from FIM, I used the Lithnet PowerShell Module, by Ryan Newington (https://lithnetrma.codeplex.com/):

01.Import-Module LithnetRMA;
02. 
03.$a=Get-Content "c:\FIMScripts\GroupNames.txt"
04. 
05.ForEach ($i in $a)
06. 
07.{
08. 
09.Write-Host $i
10. 
11.$NameSearch=Search-Resources -XPath "/Group[DisplayName = '$i']/ComputedMember” -AttributesToGet @("AccountName”)
12. 
13.$Name=$NameSearch.AccountName
14. 
15. # List of samAccountNames written to txt file
16. 
17.        $Name>>C:\users\$env:USERNAME\desktop\FIMGroups\$i.txt
18. 
19.  $GroupFile=Get-Content C:\users\$env:USERNAME\desktop\FIMGroups\$i.txt | sort-object | Out-File -Filepath C:\users\$env:USERNAME\desktop\FIMGroupsSorted\$i.txt
20. 
21.}

Then the resultant files need to be sorted alphabetically:

1.$Path = "<PathToWhereYourOutputFilesAre>"
2.$SortPath = "<PathToYourSortedFiles>"
3.$AllGroups = get-childitem $Path
4.ForEach ($Group in $AllGroups)
5.{
6.Write-Host $Group
7.$GroupFile=Get-Content $Path\$Group | sort-object | Out-File -Filepath $SortPath\$Group
8.}

Once the data is available and prepared, use WinMerge (http://winmerge.org/downloads/?lang=en) to compare the contents of the two sets of data. If all is well there should be no differences, else go fix the filters to make them match production.

Original Article: https://fim.oholics.net/fim-2010-automatic-group-management-via-the-portal/