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:
- AdvancedFilter – Allows you to specify the filter query to use.
- 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