Share via


How to Use Powershell to revert all search scopes which search on DisplayName to use Contains instead of Starts-With unless that Search Scope has the AdvancedFilter configured.

FIM ScriptBox Item

Summary

With the release of Forefront Identity Manager R2 2010, the Search Scopes have had some improvements to their overall performance.  This was achieved by changing the search scope of DisplayName to use start-with rather than contains. That is, in FIM 2010 for example if we were to do a search in the FIM Portal for all users named John then the query used to look like this:

/Person [(contains(DisplayName, 'john') or starts-with(AccountName, 'john') or 
starts-with(MailNickname, 'john'))] 

In FIM 2010 R2 the query would look like this:

/Person [(starts-with(DisplayName, 'john') or starts-with(AccountName, 'john') or 
starts-with(MailNickname, 'john'))] 

The reason for this is that in large databases, the contains query performs poorly with prefix-term searches. A prefix term refers to a string that is affixed to the front of a word to produce a derivative word or an inflected form. For a single prefix term, any word starting with the specified term will be part of the result set. For example, the term "auto*" matches "automatic", "automobile", and so forth. This change affects anything that uses or builds a Search Scope. 

Because some organizations may want to continue to use the contains operator, two new attributes have been introduced to allow the contains functionality to continue. They are:

  1. AdvancedFilter – Allows you to specify the filter query to use.
  2. DefaultSearchScopeName – Allows you to specify the search scope to use on a UocIdentityPicker.

AdvancedFilter

The AdvancedFilter box is available on the Extended Attributes of a Search Scope. You cannot specify the AdvancedFilter when creating a new Search Scope only after it has been created in the FIM Portal and you have access to the Extended Attributes.

If you want to revert all of your search scopes back to the original, FIM 2010 version you can use the following PowerShell script to accomplish this. This script checks each SearchScope within FIM to see if the DisplayName is in the list of Attributes to Search and the AdvancedFilter is not already set. If so, this will update the AdvancedFilter property to hold the necessary x-path which reverts to the "Contains" search instead of starts-with for the DisplayName attribute.

If you want to wipe out changes caused by this script, you can use this companion script.

 

Script Code

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<# .SYNOPSIS Reverts all the search scopes which search on DisplayName to use Contains instead of starts-with for the DisplayName attribute unless that Search Scope has the AdvancedFilter configured. .PARAMETER uri Address of FIM service. Default is http://localhost:5725 .PARAMETER echoOnly When supplied, only prints out messages and does not change the Search Scopes. .PARAMETER force When supplied, will overwrite any AdvancedFilters that are already configured--not null and not an empty string after being trimmed. .DESCRIPTION This checks each SearchScope within FIM to see if the DisplayName is in the list of Attributes to Search and the AdvancedFilter is not already set. If so, this will update the AdvancedFilter property to hold the necessary x-path which reverts to the "Contains" search instead of starts-with for the DisplayName attribute. .NOTES Version 1.0.0.0 - 28/06/2012 .EXAMPLE .\bulk-searchstyle-revert Reverts all the search scopes to use Contains instead of starts-with for the DisplayName Attribute, except any Search Scope where the AdvancedFilter is already configured or for which the DisplayName is not an "Attribute to Search." .EXAMPLE .\bulk-searchstyle-revert -force Takes all the search scopes which use the DisplayName as an "Attribute to Search" and reverts those search scopes to use Contains instead of starts-with including any Search Scopes where the AdvancedFilter is already configured. .EXAMPLE .\bulk-searchstyle-revert -echoOnly Prints out the Search Scope name and what the AdvancedFilter should be without actually altering the Search Scope. Useful for seeing what search scopes this function would alter and what they would be altered to. #>
[CmdletBinding()]
param(
    [string] $uri = "http://localhost:5725",
    [switch] $echoOnly,
    [switch] $force
)


# load FIM snapin, ignore errors if already loaded
Add-PSSnapin FIMAutomation -ErrorAction SilentlyContinue


# gets the value of a single-valued attribute from an exported object
function GetAttributeValue($exportObject,[string] $name) {
    $attribute = $exportObject.ResourceManagementObject.ResourceManagementAttributes | 
        Where-Object {$_.AttributeName -eq $name}
    if ($attribute.Value -ne $null)
    {
        $value = $attribute.Value
    }
    elseif($attribute.Values -ne $null)
    {
        $value = $attribute.Values
    }
    else
    {
        $value = $null
    }
    return $value
}


# Changes the specified attribute to the specified value on the supplied object
function SetAndCommitAttributeValue($object, [string] $attributeName, $attributeValue) {
    $objectID = GetAttributeValue $object "ObjectID"
# $displayName = GetAttributeValue $object "DisplayName"
    $objectType = GetAttributeValue $object "ObjectType"

# Write-Host "Setting '$displayName'.'$attributeName' to '$attributeValue'"
    
    $importChange = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportChange
    $importChange.Operation = [Microsoft.ResourceManagement.Automation.ObjectModel.ImportOperation]::Replace
    $importChange.AttributeName = ${attributeName}
    if (${attributeValue} -ne $null) {
        $importChange.AttributeValue = ${attributeValue}
    }
    $importChange.FullyResolved = 1
    $importChange.Locale = "Invariant"

    $importObject = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportObject
    $importObject.ObjectType = $objectType
    $importObject.TargetObjectIdentifier = $objectID
    $importObject.SourceObjectIdentifier = $objectID
    $importObject.State = [Microsoft.ResourceManagement.Automation.ObjectModel.ImportState]::Put
    $importObject.Changes = (,$importChange)
    
# Commit those changes to the database
    $importObject | Import-FIMConfig -uri $uri
}


# Gets the type of data that the supplied AttributeName supports.
function GetAttributeType($attributeName){
    $attribute = Export-FIMConfig -uri $uri -CustomConfig "/AttributeTypeDescription[Name='${attributeName}']"
    return GetAttributeValue $attribute "DataType"
}


# Prepares the "conditions" part of the final advanced filter.
function PrepareConditions($attributesSearched) {
    #declare an empty array for the results
    $result = @()
    for ($i=0; $i -lt $attributesSearched.Length; $i++) {
        $attribute = $attributesSearched[$i]
        $dataType = GetAttributeType $attribute
        switch($dataType) {
            "String" { 
                $value = ""
                if ($attribute -eq "DisplayName") {
                    $value = "contains(DisplayName, '%SEARCH_TERM_STRING%')"
                } else {
                    $value = "starts-with($attribute, '%SEARCH_TERM_STRING%')"
                }
                $result = $result + $value
            }
            "Reference" {
                $value = "$attribute = '%SEARCH_TERM_REFERENCE%'"
                $result = $result + $value
            }
            "Boolean" {
                $value = "$attribute = %SEARCH_TERM_BOOLEAN%"
                $result = $result + $value
            }
            "Integer" {
                $value = "$attribute = %SEARCH_TERM_INTEGER%"
                $result = $result + $value
            }
            default {
                Write-Host "Unsupported Attribute DataType: $dataType"
            }
        }
    }
    return $result
}


# builds the complete x-path for the AdvancedFilter property.
# filter = SearchScope
# attributesSearched = SearchScopeContext
function GenerateAdvancedFilter($filter, $attributesSearched) {
    $filter = $filter.Trim()
    $result = $filter
# Build up the new x-path.
    $conditions = PrepareConditions $attributesSearched
    
    if($attributesSearched.Length -gt 0 -and $conditions.Length -eq 0) {
        #failed to prepare any conditions
        return $null
    }
    
    $conditionString = [string]::Join(" or ", $conditions)

    if($filter[$filter.Length - 1] -eq "]"){
        $temp = $filter.Remove($filter.Length - 1)
        $result = $temp + ") and (" + $conditionString + ")]"
        $leftBracket = $filter.IndexOf("[")
        if($leftBracket -ge 0) {
            $result = $result.Insert($leftBracket + 1, "(")
        }
    } else {
        $result = $filter + " [(" + $conditionString + ")]"
    }
    return $result
}


# Core method for configuring the AdvancedFilter attribute to the needed value.
function ConfigureAdvancedFilter($object) {
    [Array] $attributesSearched = GetAttributeValue $object "SearchScopeContext"

#Make sure this search scope actually searches on the Display Name
    if([Array]::IndexOf($attributesSearched, "DisplayName") -ge 0) {
        $filter = GetAttributeValue $object "SearchScope"
        $advancedFilter = GenerateAdvancedFilter $filter $attributesSearched

        $displayName = GetAttributeValue $object "DisplayName"
        Write-Host "Setting AdvancedFilter on '$displayName' to '$advancedFilter'"

        if(-not $echoOnly)
        {
            SetAndCommitAttributeValue $object "msidmSearchScopeAdvancedFilter" $advancedFilter
        }
    }
}


# helper method for checking whether the supplied object has the AdvancedFilter
 # attribute configured already.
function AdvancedFilterNotSet($object) {
    $advancedFilter = GetAttributeValue $object "msidmSearchScopeAdvancedFilter"
    if([string]::IsNullOrEmpty($advancedFilter))
    {
        return $true
    } else {
        $advancedFilter = $advancedFilter.Trim()
        if($advancedFilter.Length -gt 0) {
            return $false
        } else {
            return $true
        }
    }
}

# suppress the progress indicator (makes screen unreadable with many operations)
$ProgressPreference="SilentlyContinue"

$fimtype="SearchScopeConfiguration"

# get all objects of specified type
Write-Verbose "Getting $fimtype objects..."
$objects = Export-FIMConfig -uri $uri -CustomConfig "/$fimtype" -OnlyBaseResources

if($echoOnly){
    Write-Host "No changes will be made to any search scope"
}

# iterate objects and set attributes
foreach ($object in $objects) {
    if($force -or (AdvancedFilterNotSet $object)) {
        ConfigureAdvancedFilter $object
    }
}

if($echoOnly){
    Write-Host "No changes were made to any search scope"
}

 

 

Note that this will affect the Search Scope objects used for search boxes while your Object Pickers will not be affected (i.e. not reverted to FIM 2010 functionality). If you want to revert the search functionality for Object Pickers then you must manually configure each Object Picker to employ the new DefaultSearchScopeName property. This will involve creating a new Search Scope for that Object Picker and migrating the search-scope-like properties from the Object Picker to its new Search Scope. After migrating all the Object Pickers to employ the new DefaultSearchScopeName property you can run this script to configure those new Search Scopes to employ "contains" instead of starts-with.

 

Note

To provide feedback about this article, create a post on the FIM TechNet Forum.
For more FIM related Windows PowerShell scripts, see the  FIM ScriptBox

 


See Also