{"id":2572,"date":"2013-03-23T02:31:14","date_gmt":"2013-03-23T02:31:14","guid":{"rendered":"https:\/\/www.wapshere.com\/missmiis\/?p=2572"},"modified":"2013-03-23T02:31:14","modified_gmt":"2013-03-23T02:31:14","slug":"some-housekeeping-xpath-queries","status":"publish","type":"post","link":"https:\/\/www.wapshere.com\/missmiis\/some-housekeeping-xpath-queries","title":{"rendered":"Some Housekeeping XPath Queries"},"content":{"rendered":"<p>I&#8217;ve been thinking a lot lately about maintaining data quality in the FIM Portal. When you&#8217;re working with a lot of referenced objects (and it really is the best way to go in the Portal) you find yourself copying string data around a lot. So, for example, a person selects a business unit <em>object<\/em> from an Identity Picker, but I also want to copy the business unit <em>name<\/em> to their Department field, for sync&#8217;ing to AD and other such purposes. How do we protect ourselves from unexpected failures in these processes, leading to inconsistencies in the data we&#8217;re trying to maintain?<\/p>\n<p><!--more--><\/p>\n<p>As is often the case, my colleague Bob was <a href=\"https:\/\/unifysolutions.jira.com\/wiki\/display\/FIMTEAMCOM\/Designing+and+scheduling+Housekeeping+policy+entirely+within+the+FIM+Portal\" target=\"_blank\">well ahead of me on this one<\/a> and\u00c2\u00a0has given me some good advice about targeting my queries so I find just the data inconsistencies, without unnecessarily re-running workflows against the majority\u00c2\u00a0of objects which are actually fine.<\/p>\n<p>There are a few things I&#8217;ve learnt while developing my housekeeping queries.<\/p>\n<h3>You can&#8217;t generalise for different values<\/h3>\n<p>Starting with the example above where I copy\u00c2\u00a0the name of the linked business unit object to the department field, there is no query that says &#8220;show me all people who&#8217;s Department does not equal Unit\/DisplayName&#8221;. However we can do this:<\/p>\n<pre>\/Person[Unit='c1391740-4e40-46b9-9d18-9ae39659f153' and not(Department='Accounts')]<\/pre>\n<p>the GUID there belonging to the &#8220;Accounts&#8221; business unit object which is linked to the person&#8217;s &#8220;Unit&#8221; attribute.<\/p>\n<p>So essentially we need a query for each case. We start by exporting all the business unit objects:<\/p>\n<pre>$Units= Export-FIMConfig -OnlyBaseResources -CustomConfig \"\/Unit\"<\/pre>\n<p>and then loop through them, adjusting the filter for each unit object, and fixing any errors we find:<\/p>\n<pre>foreach ($Unit in $Units)\r\n{\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0$ObjID = ($Unit.ResourceManagementObject.ObjectIdentifier).replace(\"urn:uuid:\",\"\")\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0$ObjName= ($Unit.ResourceManagementObject.ResourceManagementAttributes | where {$_.AttributeName -eq \"DisplayName\").Value\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0$Filter = \"\/Person[Unit='{0}' and not(Department=\"\"{1}\"\")]\" -f $ObjID,$ObjName\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0$ErrorObjs = Export-FIMConfig -OnlyBaseResources -CustomConfig $Filter\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0if ($ErrorObjs)\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0{\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 foreach ($obj in $ErrorObjs)\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 {\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 $ImportObject = ModifyImportObject -TargetIdentifier $obj.ResourceManagementObject.ObjectIdentifier -ObjectType \"Person\"\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 SetSingleValue -ImportObject $ImportObject -AttributeName \"Department\" -NewAttributeValue $ObjName\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 $ImportObject | Import-FIMConfig\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 }\r\n\u00c2\u00a0\u00c2\u00a0\u00c2\u00a0 }\r\n}<\/pre>\n<blockquote><p>Note: the doubled double quotes around {1} are to account for names which may have a single quote character in them.<\/p><\/blockquote>\n<h3>Multivalued Attributes<\/h3>\n<p>You may also need to check multivalued string attributes have the right values. Here again I found it more efficient to focus on the individual possible values rather than checking through a person&#8217;s whole list. But now I need two filters &#8211; one that shows missing values that should be added, and one that shows extra values that should be removed.<\/p>\n<p>In this example I have a multivalued reference field called &#8220;Applications&#8221; which links to another object type. I also copy the names of the application objects to the multivalued string attribute &#8220;ApplicationNames&#8221;, as strings are easier to use in the Sync Service. I want to check that everyone with a particular linked application also has the application name, and that no one has the application name without also having a link to the application object.<\/p>\n<pre>$AddStringFilter = \"\/Person[Applications='{0}' and not(ApplicationNames=\"\"{1}\"\")]\" -f $ObjID,$ObjName\r\n$RemoveStringFilter = \"\/Person[not(Applications='{0}') and ApplicationNames=\"\"{1}\"\"]\" -f $ObjID,$ObjName<\/pre>\n<h3>Comparison with a third object type<\/h3>\n<p>In the design I&#8217;m working on, people get applications and other services via &#8220;entitlement&#8221; objects. Once the entitlement is in an active state, a role object is directly linked to the person. So I need to be checking that people&#8217;s linked roles match their active entitlements, and also that they don&#8217;t have any linked roles where an active entitlement does not exist.<\/p>\n<pre>$AddRoleFilter = \"\/Person[ (ObjectID = \/Entitlement[Status = 'Active' and RoleLink = '{0}']\/PersonLink) and not (Applications = '{0}') ]\" -f $ObjID\r\n$RemoveRoleFilter = \"\/Person[ (Applications = '{0}') and not(ObjectID = \/Entitlement[Status = 'Active' and RoleLink = '{0}']\/PersonLink) ]\" -f $ObjID<\/pre>\n<h3>Comparison with a Set<\/h3>\n<p>The final query uses a set membership. Perhaps you have a TransitionIn Set MPR that triggers a workflow, and you want to periodically check that everyone in the Set does indeed have whatever that MPR should have given them.<\/p>\n<p>You <em>could<\/em> set the associated workflow to &#8220;Run on Policy Update&#8221; and periodically disable\/enable the MPR, however for a large set this can have a catastrophic impact on Portal performance, tying up FIM Service resources until every single member of the set has been processed, and <span style=\"text-decoration: underline;\">you can&#8217;t stop those jobs once they start<\/span>.<\/p>\n<p>So again we&#8217;re better off targeting queries to look for discrepancies, and again we&#8217;ll have to be specific &#8211; looking at individual sets and specific expected values. In this example filter I expect all members of the specified set to have an entitlement with a particular linked role:<\/p>\n<pre>$Filter = \"\/Person[ (ObjectID = \/Set[ObjectID = '{0}']\/ComputedMember) and not(ObjectID = \/Entitlement[RoleLink = '{1}']\/PersonLink)]\" -f $SetID,$RoleID<\/pre>\n<h3>Summary<\/h3>\n<p>Data quality is everything in identity management. The Sync Service\u00c2\u00a0is great at enforcing\u00c2\u00a0rules and we can re-sync the same data set as often as we need to &#8211; but the Portal operates quite differently. By putting in some extra effort with scheduled housekeeping operations we can maintain data quality &#8211; but we always need to keep performance in mind, and keep any queries and updates as efficient as possible.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve been thinking a lot lately about maintaining data quality in the FIM Portal. When you&#8217;re working with a lot of referenced objects (and it really is the best way to go in the Portal) you find yourself copying string data around a lot. So, for example, a person selects a business unit object from&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[42,60,23],"tags":[],"class_list":["post-2572","post","type-post","status-publish","format-standard","hentry","category-fim-2010","category-fim-2010-r2","category-powershell"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/pkp1o-Fu","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts\/2572","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/comments?post=2572"}],"version-history":[{"count":12,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts\/2572\/revisions"}],"predecessor-version":[{"id":2584,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/posts\/2572\/revisions\/2584"}],"wp:attachment":[{"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/media?parent=2572"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/categories?post=2572"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wapshere.com\/missmiis\/wp-json\/wp\/v2\/tags?post=2572"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}