<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>missmiis &#187; ILM 2007</title>
	<atom:link href="http://www.wapshere.com/missmiis/category/ilm/ilm2007/feed" rel="self" type="application/rss+xml" />
	<link>http://www.wapshere.com/missmiis</link>
	<description>Adventures in identity management</description>
	<lastBuildDate>Fri, 03 Feb 2012 20:41:25 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Csexport fast on a new MA, ssllooowwwwww on an upgraded one</title>
		<link>http://www.wapshere.com/missmiis/csexport-fast-on-a-new-ma-ssllooowwwwww-on-an-upgraded-one</link>
		<comments>http://www.wapshere.com/missmiis/csexport-fast-on-a-new-ma-ssllooowwwwww-on-an-upgraded-one#comments</comments>
		<pubDate>Mon, 17 Oct 2011 01:12:38 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[Performance]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1728</guid>
		<description><![CDATA[This is just an observation, and if anyone&#8217;s got any ideas or suggestions do let me know.
I&#8217;m working on a FIM Sync service that I&#8217;ve upgraded from ILM by the database transfer method. I had to recreate one MA completely, and re-import and re-join all it&#8217;s objects.
I was running some csexports against a few of [...]]]></description>
			<content:encoded><![CDATA[<p>This is just an observation, and if anyone&#8217;s got any ideas or suggestions do let me know.</p>
<p>I&#8217;m working on a FIM Sync service that I&#8217;ve upgraded from ILM by the database transfer method. I had to recreate one MA completely, and re-import and re-join all it&#8217;s objects.</p>
<p>I was running some csexports against a few of the MAs to get info about pending exports and noticed this:</p>
<ul>
<li>csexport against the upgraded MAs was extremely slow, but</li>
<li>csexport against the new MA was super fast.</li>
</ul>
<p><span id="more-1728"></span></p>
<p>The MAs all have roughly similar numbers of objects (15-20k) and the number of pending exports for each MA was in the low hundreds.</p>
<p>So why the big difference?</p>
<p>My first theory was that the data for the upgraded MAs was scattered around in the DB, while the re-imported MA had nicely consecutive rows. So I deleted and re-imported the CS for one of the upgraded MAs. Result: csexport still slow.</p>
<p>I wondered if perhaps the upgraded MAs had some structural hangover from ILM that slowed them down &#8211; but nothing jumps out at me in the database, and when I ran a SQL trace while running the csexports I could see the same stored procedure being called for all of them.</p>
<p>So I&#8217;m still stumped. I suppose another good test would be to completely delete and re-create one of the MAs &#8211; but I&#8217;m not going to slow this project down just to satisfy my curiosity.</p>
<p>If anyone has any ideas about what&#8217;s going on I&#8217;d be glad to hear!</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/csexport-fast-on-a-new-ma-ssllooowwwwww-on-an-upgraded-one/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>More fun with powershell and XML &#8211; getting flow rule source attributes from a MA config file</title>
		<link>http://www.wapshere.com/missmiis/more-fun-with-powershell-and-xml-getting-flow-rule-source-attributes-from-a-ma-config-file</link>
		<comments>http://www.wapshere.com/missmiis/more-fun-with-powershell-and-xml-getting-flow-rule-source-attributes-from-a-ma-config-file#comments</comments>
		<pubDate>Fri, 07 Oct 2011 07:07:19 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>
		<category><![CDATA[Office 365]]></category>
		<category><![CDATA[powershell]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1717</guid>
		<description><![CDATA[Today I was trying to find a list of all attributes synchronized to Office365 by DirSync. I couldn&#8217;t find a published list so I figured I&#8217;d just get it from the MA, using powershell to extract the list of source attributes. The list of AD user attributes synchronized by DirSync is at the bottom of this [...]]]></description>
			<content:encoded><![CDATA[<p>Today I was trying to find a list of all attributes synchronized to Office365 by DirSync. I couldn&#8217;t find a published list so I figured I&#8217;d just get it from the MA, using powershell to extract the list of source attributes. The list of <strong>AD user</strong> attributes synchronized by DirSync is at the bottom of this post, and in between I&#8217;ll show you how I got there.</p>
<p>Note while I&#8217;m talking DirSync here this method will work for MIIS, ILM and FIM Management Agents.</p>
<p><span id="more-1717"></span></p>
<h3>Export the MA</h3>
<p>DirSync is just ILM and you can get into the Identity Manager client by running:</p>
<p>C:\Program Files\Microsoft Online Directory Sync\SYNCBUS\UIShell\miisclient.exe</p>
<p>Note: If you get an error about not having rights then add your account into the MIISAdmins group on the DirSync server then re-login.</p>
<p>Open the &#8220;Management Agents&#8221; page, click on the &#8220;SourceAD&#8221; MA to highlight it, then choose &#8220;Export Management Agent&#8221; from the Actions menu., and save the XML file.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/dirsync-parseMA-01.jpg"><img class="alignnone size-full wp-image-1718" title="dirsync-parseMA 01" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/dirsync-parseMA-01.jpg" alt="" width="629" height="292" /></a></p>
<h3>Parse the XML file in Powershell</h3>
<p>Now I go into powershell and load the content of the xml file into a variable:</p>
<pre>PS C:\scripts&gt; [xml]$xml = get-content .\dirsync-sourceAD.xml</pre>
<p>I can see all the import attribute flows like this:</p>
<pre>PS C:\scripts&gt; $xml."export-ma"."mv-data"."import-attribute-flow"."import-flow-set"

mv-object-type                                                                              import-flows
--------------                                                                              ------------
group                                                                                       {import-flows, import-flows, import-flows, import-flows...}
contact                                                                                     {import-flows, import-flows, import-flows, import-flows...}
person                                                                                      {import-flows, import-flows, import-flows, import-flows...}</pre>
<p>Right now I&#8217;m only interested in the &#8220;person&#8221; attribute flows:</p>
<pre>PS C:\scripts&gt; $xml."export-ma"."mv-data"."import-attribute-flow"."import-flow-set"[2]

mv-object-type                                                                              import-flows
--------------                                                                              ------------
person                                                                                      {import-flows, import-flows, import-flows, import-flows...}</pre>
<p>This next one lists the defined attribute flows. There&#8217;s a lot more but I&#8217;m just showing the top few.</p>
<pre>PS C:\scripts&gt; $xml."export-ma"."mv-data"."import-attribute-flow"."import-flow-set"[2]."import-flows"

mv-attribute                                                  type                                                         import-flow
------------                                                  ----                                                         -----------
assistant                                                     ranked                                                       {import-flow, import-flow}
company                                                       ranked                                                       {import-flow, import-flow}
department                                                    ranked                                                       {import-flow, import-flow}
displayName                                                   ranked                                                       {import-flow, import-flow}
facsimileTelephoneNumber                                      ranked                                                       {import-flow, import-flow}
givenName                                                     ranked                                                       {import-flow, import-flow}</pre>
<p>It might look as though the mv-attribute is what I want, but actually that&#8217;s an internal sync service attribute, and not the Active Directory attribute. I need to go a little deeper into the xml to get that.</p>
<p>First I&#8217;ll define a new variable to simplify my xml path:</p>
<pre>PS C:\scripts&gt; $importflows = $xml."export-ma"."mv-data"."import-attribute-flow"."import-flow-set"[2]."import-flows"</pre>
<p>Then all I have to do is loop through picking out the &#8220;src-attribute&#8221;. The results are listed below:</p>
<pre>PS C:\scripts&gt; foreach ($iaf in $importflows) {$iaf."import-flow"[0]."direct-mapping"."src-attribute"}
assistant
company
department
displayName
facsimileTelephoneNumber
givenName
homePhone
initials
manager
mobile
physicalDeliveryOfficeName
postalCode
telephoneNumber
title
mailNickname
ipPhone
middleName
otherFacsimileTelephoneNumber
otherHomePhone
otherIpPhone
otherMobile
otherPager
otherTelephone
pager
countryCode
description
info
streetAddress
wWWHomePage
url
extensionAttribute11
extensionAttribute12
extensionAttribute13
extensionAttribute14
extensionAttribute15
mail
cn
l
co
sn
st
postOfficeBox
msRTCSIP-UserEnabled
legacyExchangeDN
msExchHideFromAddressLists
msExchMailboxGuid
msExchAssistantName
msDS-HABSeniorityIndex
msDS-PhoneticDisplayName
msExchArchiveGUID
msExchBypassModerationFromDLMembersLink
msExchBypassModerationLink
msExchEnableModeration
msExchImmutableId
msExchModeratedByLink
msExchModerationFlags
msExchRecipientDisplayType
msExchResourceCapacity
preferredLanguage
publicDelegates
telephoneAssistant
msExchResourceDisplay
thumbnailPhoto
msExchBlockedSendersHash
msExchSafeRecipientsHash
msExchSafeSendersHash
extensionAttribute1
extensionAttribute10
extensionAttribute2
extensionAttribute3
extensionAttribute4
extensionAttribute5
extensionAttribute6
extensionAttribute7
extensionAttribute8
extensionAttribute9
msExchResourceMetaData
msExchResourceSearchProperties
msRTCSIP-Line
msRTCSIP-PrimaryUserAddress
targetAddress
msExchSenderHintTranslations
msExchArchiveName
msRTCSIP-DeploymentLocator
msExchRemoteRecipientType
msExchLitigationHoldDate
msExchLitigationHoldOwner
msExchRecipientTypeDetails
msExchRetentionComment
msExchRetentionURL
msExchAuditAdmin
msExchAuditDelegate
msExchAuditDelegateAdmin
msExchBypassAudit
msExchDelegateListLink
msExchELCExpirySuspensionEnd
msExchELCExpirySuspensionStart
msExchELCMailboxFlags
msExchMailboxAuditEnable
msExchMailboxAuditLogAgeLimit
msRTCSIP-OptionFlags
msExchUsageLocation

____________________________________________________________________________________________________________________________________________________________________________________</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/more-fun-with-powershell-and-xml-getting-flow-rule-source-attributes-from-a-ma-config-file/feed</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Using powershell to parse a csexport file</title>
		<link>http://www.wapshere.com/missmiis/using-powershell-to-parse-a-csexport-file</link>
		<comments>http://www.wapshere.com/missmiis/using-powershell-to-parse-a-csexport-file#comments</comments>
		<pubDate>Sun, 25 Sep 2011 04:31:15 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[FIM Sync Service]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[Logs]]></category>
		<category><![CDATA[MIIS 2003]]></category>
		<category><![CDATA[powershell]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1710</guid>
		<description><![CDATA[From time to time it&#8217;s necessary to access detailed data about objects in the connector space of a FIM Sync MA. One way to do this is with the csexport command line tool (found in the Bin folder) but the XML it produces isn&#8217;t particularly pretty and it doesn&#8217;t open nicely in Excel.
Luckily powershell has [...]]]></description>
			<content:encoded><![CDATA[<p>From time to time it&#8217;s necessary to access detailed data about objects in the connector space of a FIM Sync MA. One way to do this is with the <strong>csexport</strong> command line tool (found in the Bin folder) but the XML it produces isn&#8217;t particularly pretty and it doesn&#8217;t open nicely in Excel.</p>
<p>Luckily powershell has some great XML parsing capability, so here&#8217;s a little script I wrote which takes an XML file created by csexport, and produces a CSV file more suitable for opening in Excel. Note that the script only supports single-valued attributes &#8211; you can modify it yourself if you need multi-values.</p>
<p><span id="more-1710"></span><br />
<code></code></p>
<pre>#
# parse-csexport.ps1
#

# Change the following list to get different attributes. The first four are available for all connector spaces.
$csvcolumns = @("dn","connector-type","connector-state","mv-guid","emailAddress","userName","sn","givenName","title","personalTitle")

$csvfile = "csexport.csv"
$csexportfile = "csexport.xml"

[xml]$csexport = get-content $csexportfile

if (Test-Path $csvfile) {Remove-Item -Path $csvfile}
foreach ($csvcol in $csvcolumns) {
  $csvheader = $csvheader + ";" + $csvcol
}
Add-Content $csvfile $csvheader

foreach ($csobj in $csexport."cs-objects"."cs-object") {
  $csobjhash = @{}
  $csobjhash.Add("dn",$csobj."cs-dn")
  # Disconnectors
  if ($csobj.connector -eq "0") {
    $csobjhash.Add("connector-type","disconnector")
    $csobjhash.Add("connector-state",$csobj."connector-state")
    $csobjhash.Add("mv-guid","")
    foreach ($attr in $csobj."unapplied-export-hologram".entry.attr) {
      if ($attr.multivalued -eq "false") {
        $csobjhash.Add($attr.name,$attr.value)
      }
    }
  }
  # Connectors
  else {
    $csobjhash.Add("connector-type","connector")
    $csobjhash.Add("connector-state",$csobj."connector-state")
    $csobjhash.Add("mv-guid",$csobj."mv-link"."#text")
    foreach ($attr in $csobj."synchronized-hologram".entry.attr) {
      if ($attr.multivalued -eq "false") {
        $csobjhash.Add($attr.name,$attr.value)
      }
    }
  }
  $csvline = ""
  foreach ($csvcol in $csvcolumns) {
    if ($csobjhash.Contains($csvcol)) {
      if ($csvline -eq "") {$csvline = $csobjhash.Item($csvcol) }
      else {$csvline = $csvline + ";" + $csobjhash.Item($csvcol) }
    }
    else {$csvline = $csvline + ";"}
  }
  $csvline
  Add-Content $csvfile $csvline
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/using-powershell-to-parse-a-csexport-file/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Overcoming multiple personality disorder in the source data</title>
		<link>http://www.wapshere.com/missmiis/overcoming-multiple-personality-disorder-in-the-source-data</link>
		<comments>http://www.wapshere.com/missmiis/overcoming-multiple-personality-disorder-in-the-source-data#comments</comments>
		<pubDate>Mon, 22 Nov 2010 08:16:52 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>
		<category><![CDATA[VB.NET]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1063</guid>
		<description><![CDATA[This post is going to describe an approach I&#8217;ve used a couple of times this year, in situations which looked quite different at the outset, but actually had one particular characteristic in common: the source data contained multiple records relating to individual people, and I needed access to all of them.
One case involves &#8220;contract&#8221; records [...]]]></description>
			<content:encoded><![CDATA[<p>This post is going to describe an approach I&#8217;ve used a couple of times this year, in situations which looked quite different at the outset, but actually had one particular characteristic in common: the source data contained multiple records relating to individual people, and I needed access to all of them.</p>
<p>One case involves &#8220;contract&#8221; records from an HR database &#8211; a person who had moved around the company may have several. The second is an LDAP data source for a univeristy where an inidvidual may have both staff and student profiles simultaneously, and as a student, may have multiple active &#8220;enrollments&#8221;.<br />
<span id="more-1063"></span></p>
<h3>In an ideal situation</h3>
<p>There is a key best practise at stake here, which I believe becomes even more significant when you put the FIM Portal into the mix:</p>
<blockquote><p><strong>Each individual should only be represented <u>once</u> in the Metaverse.</strong></p></blockquote>
<p>In an ideal situation you will project each person only once from your source data. Changes to contract, enrollment etc would be treated simply as attribute updates to the core Metaverse object, which remains the same.</p>
<p>If your source data lists people multiple times, as in my examples above, you might do some automated manipulation prior to presenting the data to the Sync Service &#8211; selecting the best representation of each person and merging data where appropriate. Then you can have a nice one-to-one relationship between your projectoin source and your Metaverse.</p>
<p><img src="http://www.wapshere.com/images/mpd/ideal.jpg" alt="" /></p>
<h3>But what if you need access to those individual contracts?</h3>
<p>This was my problem. In the case of the HR database contract records, the local IT only had access to the <em>contract number</em>. For them the important information related to the person&#8217;s job at that site. But for us, centrally, looking to automate moves and changes across the organisation, we needed a way to link those seperate contract numbers to one individual.</p>
<p>At first, and against my better judgement, we started off projecting the contracts into the Metaverse as seperate people. I was assured that, when people moved jobs, all their old accounts were deleted including their mailbox. Pretty quickly this turned out to be not the case at all, and I started with the messy gymnastics needed to reconnect existing accounts to the newer better contract in the Metaverse, without actually deleting anything.</p>
<p><img src="http://www.wapshere.com/images/mpd/messy.jpg" alt="" /></p>
<h3>Step One in solving data source MPD: Split the data into &#8220;Persons&#8221; and &#8220;Contracts&#8221;</h3>
<p>When it became clear that a redesign was necessary, I started by splitting the data into &#8220;persons&#8221; and &#8220;contracts&#8221;. In fact for the university this was already the case, but with the HR database I had to use some SQL queries in an SSIS package to split the data into &#8220;objectType=person&#8221; and &#8220;objectType=contract&#8221; rows. Clearly a unique identifier is needed for both persons and contracts.</p>
<p>My <strong>persons</strong> have the following basic information:</p>
<ul>
<li>objectType (=person)</li>
<li>personID</li>
<li>Data that should be independant of the contracts, like name, gender, date of birth.</li>
</ul>
<p>My <strong>contracts</strong> must include the personID so I can join them correctly:</p>
<ul>
<li>objectType (=contract)</li>
<li>contractID</li>
<li>personID</li>
<li>Data that is specific to the contract, like department, job title, start and end dates.</li>
</ul>
<h3>Create Seperate MAs for Persons and Contracts</h3>
<p>Once the data was seperated I created different MAs for persons and contracts. The aim is to project the person <em>only once</em> into the metaverse, and then join the changeable contract records from the second MA.</p>
<p><img src="http://www.wapshere.com/images/mpd/multijoin.jpg" alt="" /></p>
<h3>But what about the Import Flows?</h3>
<p>The big problem here is that you want to flow data into the metaverse from the Contracts MA. If multiple contracts are joined you&#8217;ll get the <strong>ambiguous-import-flow-from-multiple-connectors</strong> error. Besides which you probably don&#8217;t want to be importing from multiple contracts anyway. In my case (both cases in fact) I had to try and choose the &#8220;best&#8221; contract and then just flow the data from that one. As the &#8220;best&#8221; could change I have to re-evaluate the selection each time.</p>
<h3>Why not just join one contract at a time?</h3>
<p>The cleanest setup would be to only have one contract joined to the Metaverse object. The IAF rules are then very simple.</p>
<p>But, unless you can block all except <em>one contract per person</em> using Connector Filters, you may find this tricky to achieve.</p>
<p>Partly the problem is the way the Sync Service does its sync&#8217;ing &#8211; that is, it works on <em>one connector space object at a time</em>. Additionally, the only other connector space objects accessible during this sync operation are those that can be found by <em>following joins</em>.</p>
<p>So, while sync&#8217;ing <em>this</em> connector space object we can only know about <em>other</em> connector space objects where <em>a direct join relationship exists.</em></p>
<p>This essentially means I can only figure out if the current CS object is really the best choice if all possible contracts are joined to the Metaverse person at the same time.</p>
<h3>Blocking Import Flows</h3>
<p>So here&#8217;s where I ended up.</p>
<ol>
<li>I let the multiple contracts be joined to the Metaverse person, but I only allow import flows from one of the contracts.</li>
<li>I write the &#8220;preferred contract&#8221; number into an attribute on the person object.</li>
<li>When sync&#8217;ing a CS object I compare it to the current &#8220;preferred contract&#8221;. If this one is better I replace the contract number on the Metaverse object.</li>
<li>All the import flows (which must <em>all </em>be Advanced rules) check if this CS object is the preferred contract before allowing (or skipping) the flow.</li>
</ol>
<p>While my IAF rules are now more complex, there have been definite advantages to this approach. The non-preferred contracts are still accessible from the Metaverse object and this has allowed us to more easily identify errors in the source data. Sometimes we have forced a particular contract to be preferred by disconnecting the currently preferred contract and syncing another one.</p>
<p>There is also the benefit of having some information as opposed to none. If I was, for example, blocking all expired contracts from entering the connector space in the first place I would be losing valuable deprovisioning information. If the best pick contract for a person is an expired one, I still want to see it.</p>
<p><img src="http://www.wapshere.com/images/mpd/blockimport.jpg" alt="" /></p>
<h3>Flow Blocking Method 1: The Double Sync</h3>
<p>I have tried out two different ways of blocking IAFs. The first is simpler to implement but the downside is <strong>you have to Sync twice</strong>. Because the evaluation of the contract is done as part of the &#8220;import_contractID&#8221; rule, and this might happen at any point in the set of flow rules, to ensure all IAFs run for the new preferred contract you have to sync twice.</p>
<p><code></p>
<pre>Case "import_contractID"
  If mventry("contractID").Value &lt;&gt; csentry("contractID").Value
      <em>Whatever comparisons you need to do to see if this contract is preferable to the values
      already imported to the Metaverse object. If it's better, replace the contractID.</em>
  End If

Case "import_otherAttribute"
  '<em>Replicates a Direct IAF</em>
  If mventry("contractID").IsPresent AndAlso mventry("contractID").Value = csentry("contractID").Value
    If csentry("otherAttribute").IsPresent Then
      mventry("otherAttribute").Value = csentry("otherAttribute").Value
    Else
      mventry("otherAttribute").Delete()
    End If
  End If</pre>
<p> </code> </p>
<h3>Flow Blocking Method 2: The First Touch</h3>
<p>In an effort to avoid the double-sync I have started using another method where I run my comparison tests above the Case statement, and then set a Utils.TransactionProperties to show the test has run.</p>
<blockquote><p>Utils.TransactionProperties is the way to pass values between different bits of code run as part of the same object&#8217;s Sync operation.</p></blockquote>
<p>While I now only need to sync once, I still don&#8217;t know which IAF rule will get called first, so <b>I have to pass all comparison CS attributes to each IAF rule</b>.</p>
<p>Here&#8217;s what the MapAttributesForImport Sub looks like now:</p>
<p><code></p>
<pre>Public Sub MapAttributesForImport(ByVal FlowRuleName As String, ByVal csentry As CSEntry, ByVal mventry As MVEntry) Implements IMASynchronization.MapAttributesForImport

 <em> '' Evaluate this CS object to see if it is a better source for attribute flows to the joined Metaverse object.
  '' Using the Utils.TransactionProperties flag will ensure this test is made only at the start of sync'ing the CS object.</em>

  If Not Utils.TransactionProperties.Contains("FlowEnabled") Or Not mventry("contractID").IsPresent Then

    Dim BestMatch As Boolean = False
    If mventry("contractID").IsPresent Then
      If mventry("contractID").Value = csentry("contractID").Value Then
        BestMatch = True
      Else
          <em>Comparison tests - set BestMatch=True if this is a better match.</em>
      End If

      If BestMatch Then
        Utils.TransactionProperties("FlowEnabled") = csentry("contractID").Value
        <em>  ''Note: I set this to the contractID to avoid unexpected behaviour following a disconnection.</em>
      Else
        Utils.TransactionProperties("FlowEnabled") = "false"
      End If

    End If

    <em>'' Do not continue with flow rules if this CS object has a different id to the best match.</em>
    If Not Utils.TransactionProperties("FlowEnabled").Equals(csentry("contractID").Value) Then
      Exit Sub
    End If

    Select Case FlowRuleName

      Case "Import_contractID"
        mventry("contractID").Value = csentry("contractID").Value

      Case "Import_otherAttribute"
        If csentry("otherAttribute").IsPresent Then
          mventry("otherAttribute").Value = csentry("otherAttribute").Value
        Else
          mventry("otherAttribute").Delete()
        End If

      ....

  End Select
End Sub</pre>
<p></code></p>
<h3>Importing a reference</h3>
<p>There is one final advantage I can extract out of seperating my data into persons and contracts &#8211; I can, if I need to, also import &#8220;contract&#8221; objects into the Metaverse and, with the addition of a <a href="http://www.wapshere.com/missmiis/configuration-of-the-sql-ma">multivalue table</a> (for database MAs) I can construct a reference attribute that directly links the person to their contract ojects. This would be very useful if I wanted a person&#8217;s contracts to be accessible in the FIM Portal.</p>
<p><img src="http://www.wapshere.com/images/mpd/reference.jpg" alt="" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/overcoming-multiple-personality-disorder-in-the-source-data/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Joining on the Metaverse GUID</title>
		<link>http://www.wapshere.com/missmiis/joining-on-the-metaverse-guid</link>
		<comments>http://www.wapshere.com/missmiis/joining-on-the-metaverse-guid#comments</comments>
		<pubDate>Mon, 15 Nov 2010 15:22:43 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1078</guid>
		<description><![CDATA[Just a quick note on this as it&#8217;s something that wasn&#8217;t immediately obvious to me.
I was using the Metaverse GUID as an anchor for provisioning to a simple SQL MA like so:
csentry("mvguid").StringValue = mventry.ObjectID.ToString
However when I tried to do a direct join rule on csentry:mvguid = mventry:&#60;object-id&#62; I got  the error cannot-parse-object-id.
In fact the operation [...]]]></description>
			<content:encoded><![CDATA[<p>Just a quick note on this as it&#8217;s something that wasn&#8217;t immediately obvious to me.</p>
<p>I was using the Metaverse GUID as an anchor for provisioning to a simple SQL MA like so:</p>
<p><code>csentry("mvguid").StringValue = mventry.ObjectID.ToString</code></p>
<p>However when I tried to do a direct join rule on csentry:mvguid = mventry:&lt;object-id&gt; I got  the error <strong>cannot-parse-object-id</strong>.</p>
<p>In fact the operation above had removed the brackets from the Guid string and I had to add them back in using an Advanced Join Rule:</p>
<p><code>values.Add("{" &#038; csentry("mvguid").StringValue &#038; "}")</code></p>
<p>Of course the other way would have been to add the brackets back in when setting the mvguid value on the csentry in the first place, but I&#8217;d already provisioned quite a few objects. I&#8217;ll do it like that next time.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/joining-on-the-metaverse-guid/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>XML Lookup file</title>
		<link>http://www.wapshere.com/missmiis/xml-lookup-file</link>
		<comments>http://www.wapshere.com/missmiis/xml-lookup-file#comments</comments>
		<pubDate>Fri, 05 Nov 2010 10:25:23 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>
		<category><![CDATA[VB.NET]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1039</guid>
		<description><![CDATA[On my first ever MIIS project we were an OCG customer so were able to use their nice XML library. I can&#8217;t remember in great detail what it did, but I&#8217;ve always considered the concept a best practise: if there&#8217;s anything that you find yourself hard-coding as a constant value in extension code, then you [...]]]></description>
			<content:encoded><![CDATA[<p>On my first ever MIIS project we were an OCG customer so were able to use their nice XML library. I can&#8217;t remember in great detail what it did, but I&#8217;ve always considered the concept a best practise: if there&#8217;s anything that you find yourself hard-coding as a constant value in extension code, then you should put it in a lookup file, where you can change the values later without having to recompile the code.<br />
<span id="more-1039"></span></p>
<p>I&#8217;ve been working on a couple of projects this year which are big and complicated enough to warrant a proper lookup file, so I&#8217;ve finally had to get in and figure out how to parse XML in .NET. As usual this post is just about things I&#8217;ve been doing and I&#8217;m not trying to say this is the best way to go. You will have to adapt to your own requirements.    </p>
<h3>The Structure of the XML File</h3>
<p>The general outline of my XML file is to have a section for Global settings, and then individual sections for each MA. Note the &#8220;tag&#8221; for each MA, which I&#8217;ll be using in the code.  The specifc values you include for each MA are entirely up to your requirements.</p>
<pre>&lt;SyncParams&gt;
  &lt;Global&gt;
    &lt;SyncService&gt;
      &lt;ServerName&gt;mysyncserver.mydomain.com&lt;/ServerName&gt;
      &lt;IsDev&gt;False&lt;/IsDev&gt;
    &lt;/SyncService&gt;
    &lt;SQL&gt;
      &lt;Server&gt;mysqlserver.mydomain.com&lt;/Server&gt;
    &lt;/SQL&gt;
  &lt;/Global&gt;
  &lt;ManagementAgents&gt;
    &lt;MA name='FIM Portal' tag='MAName_FIM'&gt;
      &lt;maType&gt;FIM&lt;/maType&gt;
      &lt;MVobjectType&gt;person&lt;/MVobjectType&gt;
      &lt;MVobjectType&gt;group&lt;/MVobjectType&gt;
      &lt;MVobjectSource&gt;group&lt;/MVobjectSource&gt;
    &lt;/MA&gt;
    &lt;MA name='SQL HR Employees' tag='MAName_HR'&gt;
      &lt;maType&gt;SQL&lt;/maType&gt;
      &lt;MVobjectType&gt;person&lt;/MVobjectType&gt;
      &lt;MVobjectSource&gt;person&lt;/MVobjectSource&gt;
    &lt;/MA&gt;
    &lt;MA name='Notes' tag='MAName_Notes'&gt;
      &lt;maType&gt;NOTES&lt;/maType&gt;
      &lt;MVobjectType&gt;person&lt;/MVobjectType&gt;
      &lt;MVobjectSource&gt;person&lt;/MVobjectSource&gt;
    &lt;/MA&gt;
    &lt;MA name='AD mydomain' tag='MAName_AD'&gt;
      &lt;maType&gt;AD&lt;/maType&gt;
      &lt;provEnabled&gt;True&lt;/provEnabled&gt;
      &lt;MVobjectType&gt;person&lt;/MVobjectType&gt;
      &lt;MVobjectType&gt;group&lt;/MVobjectType&gt;
      &lt;OU_USERS_INTERNAL&gt;OU=Internal,OU=Users,OU=MyOrg,DC=mydomain,DC=com&lt;/OU_USERS_INTERNAL&gt;
      &lt;OU_USERS_EXTERNAL&gt;OU=External,OU=Users,OU=MyOrg,DC=mydomain,DC=com&lt;/OU_USERS_EXTERNAL&gt;
      &lt;OU_USERS_GENERIC&gt;OU=Generic,OU=Users,OU=MyOrg,DC=mydomain,DC=com&lt;/OU_USERS_GENERIC&gt;
      &lt;OU_USERS_RESIGNED&gt;OU=Resigned,OU=Users,OU=MyOrg,DC=mydomain,DC=com&lt;/OU_USERS_RESIGNED&gt;
      &lt;OU_GROUPS&gt;OU=Groups,OU=MyOrg,DC=mydomain,DC=com&lt;/OU_GROUPS&gt;
      &lt;OU_CONTACTS&gt;OU=Contacts,OU=MyOrg,DC=mydomain,DC=com&lt;/OU_CONTACTS&gt;
    &lt;/MA&gt;
  &lt;/ManagementAgents&gt;
&lt;/SyncParams&gt;</pre>
<h3>Initialization</h3>
<p>You can use the Initialization section of your extension code to open and load the XML lookup file. Note that you&#8217;ll have to do this in each extension project where you want to access parameters from the file. Your code will also need a reference to <strong>System.Xml</strong>.    </p>
<pre>    Dim xmlParams As Xml.XmlDocument

    Public Sub Initialize() Implements IMVSynchronization.Initialize
        xmlParams = New Xml.XmlDocument
        Dim xmlPath As String = Utils.ExtensionsDirectory &amp; "\SyncParams.xml"

        Try
            xmlParams.Load(xmlPath)
        Catch ex As Exception
            Throw New ExtensionException("Failed while opening XML parameter file. " &amp; ex.Message)
        End Try
    End Sub    </pre>
<h3>Accessing the Global parameters</h3>
<p>TheGlobal parameters are easy to get to because there&#8217;s only one node for them. So to retrieve the server name from the XML file above I&#8217;d just do this:  </p>
<pre>    xmlParams.SelectSingleNode("//Global/SyncService/ServerName").InnerText   </pre>
<h3>Accessing a specific MA&#8217;s Parameters</h3>
<p>The next thing you will probably want to do is access the values for a specifc MA. I&#8217;m using the &#8220;tag&#8221; attribute as a way to identitfy the correct node. In this example I retrieve an OU name from the &#8221;AD mydomain&#8221; parameters.   </p>
<pre>    xmlParams.SelectSingleNode("//ManagementAgents/MA[@tag='MAName_AD']/OU_USERS_EXTERNAL").InnerText</pre>
<h3>Creating a Hash Table of MA Names</h3>
<p>The other thing that can be useful is to make a hash table of MA Names for easy reference. First you need to define a global variable:  </p>
<pre>    Dim MA_Names As New Collection</pre>
<p>Then you add something like this to the Initialization sub:  </p>
<pre>    For Each xmlNode In xmlParams.SelectNodes("//ManagementAgents/MA")
        MA_Names.Add(xmlNode.Attributes("name").Value, xmlNode.Attributes("tag").Value)
    Next</pre>
<p>Now in the rest of the project, when you want to use an MA&#8217;s name, you only need refer to it using its tag:  </p>
<pre>    MA_Names.Item("MAName_AD").ToString </pre>
<h3>Selectively disabling provisioning</h3>
<p>Another thing you might like to do, especially if you have a number of MAs that you are provisioning to, is to be able to selectively disable the provisioning code for one of them just by using the XML file.</p>
<p>Obviously you&#8217;ll have to modify your provisioning code to check the value of the flag. In the example above I would need to test for the value of &#8220;provEnabled&#8221; before calling the provisioning code for the &#8220;AD mydomain&#8221; MA:</p>
<pre>
    If xmlParams.SelectSingleNode("//ManagementAgents/MA[@tag='MAName_AD']/provEnabled").InnerText = "True" then
        Provision_AD(mventry)
    End If</pre>
<h3>Conclusion</h3>
<p>Of course all of this takes more effort in the coding but the code will be more robust if it avoids hard-coded values, and it will be easier for others to make simple changes.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/xml-lookup-file/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>OpenLDAP Provisioning</title>
		<link>http://www.wapshere.com/missmiis/openldap-provisioning</link>
		<comments>http://www.wapshere.com/missmiis/openldap-provisioning#comments</comments>
		<pubDate>Fri, 27 Aug 2010 11:16:59 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>
		<category><![CDATA[OpenLDAP]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=882</guid>
		<description><![CDATA[After getting the OpenLDAP XMA working on FIM I hoped it would be possible to provision to it using FIM codeless sync. Unfortunately the conclusion I have come to is No, it isn&#8217;t. 
The sticking problem is the objectClass. Typically objects in LDAP will have more than one objectClass, and the only way to set them in MIIS/ILM [...]]]></description>
			<content:encoded><![CDATA[<p>After getting the <a href="http://www.wapshere.com/missmiis/compiling-the-openldap-xma-to-use-with-fim-2010">OpenLDAP XMA working on FIM</a> I hoped it would be possible to provision to it using FIM codeless sync. Unfortunately the conclusion I have come to is No, it isn&#8217;t.<span id="more-882"></span> </p>
<p>The sticking problem is the objectClass. Typically objects in LDAP will have more than one objectClass, and the only way to set them in MIIS/ILM is in the metaverse extension code. It is no different with FIM. There is no way to set objectClass from a codeless sync rule, and you can&#8217;t set it in an export attribute flow. </p>
<p>So a Metaverse extension is still the way to go. Here is an example of a provisioning rule for OpenLDAP. I am creating a posixAccount, mostly as a way of showing how to add the dependant objectClasses. Your objectClasses will no doubt vary.</p>
<p>I have included a couple of functions that I am using to generate a password hash and to find the next uidNumber. Note for the latter function the Terminate sub is also implicated.</p>
<div></div>
<p><code></p>
<pre>Imports Microsoft.MetadirectoryServices
Imports System.DirectoryServices
Imports System.Text
Imports System.IO
Imports System.Security.Cryptography

Public Class MVExtensionObject
    Implements IMVSynchronization

    '' It is a better practise to put these constants in a lookup file
    Const OL_SERVER As String = "myldapserver.mydomain.com"
    Const OL_ROOT As String = "dc=myldap,dc=mydomain,dc=com"
    Const OL_OU_USERS As String = "ou=users,dc=myldap,dc=mydomain,dc=com"
    Const OL_UIDFILE As String = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\MaData\openLDAP\uidNumber.txt"
    Const OL_READ_USER As String = "cn=fimread,ou=users,dc=myldap,dc=mydomain,dc=com"
    Const OL_READ_PW As String = "password"
    Const OL_DEFAULT_GROUP As String = "1000"

    Public Sub Initialize() Implements IMVSynchronization.Initialize
        ' TODO: Add initialization code here
    End Sub

    Public Sub Terminate() Implements IMVSynchronization.Terminate
    '' Deleting this file forces a new LDAP search the next time, allowing for accounts which may have been created manually.
        If File.Exists(OL_UIDFILE) Then
            File.Delete(OL_UIDFILE)
        End If
    End Sub

    Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision
        Select Case mventry.ObjectType
            Case "person"
                Provision_OpenLDAPPerson(mventry)
        End Select
    End Sub

    Sub Provision_OpenLDAPPerson(ByVal mventry As MVEntry)
        Dim ShouldExist As Boolean = False
        Dim DoesExist As Boolean
        Dim CSEntry As CSEntry
        Dim OLMA As ConnectedMA = mventry.ConnectedMAs("openLDAP")
        Dim expectedDN As ReferenceValue
        Dim Container As String

        '' Should the account exist? (Add more logic here to decide)
        ShouldExist = True

        '' Does the account already exist?
        If OLMA.Connectors.Count = 0 Then
            DoesExist = False
        Else
            DoesExist = True
        End If

        '' Generate the expected DN for the user - to use in creating or moving
        If mventry("accountName").IsPresent And ShouldExist Then
            Dim RDN As String = "cn=" &amp; mventry("accountName").StringValue.ToLower
            expectedDN = OLMA.EscapeDNComponent(RDN).Concat(OL_OU_USERS)
        End If

        '' Take action based on values of ShouldExist and DoesExist
        If ShouldExist And DoesExist Then
            '' Check if rename needed
            If csentry.DN.ToString.ToLower &lt;&gt; expectedDN.ToString.ToLower Then
                csentry.DN = expectedDN
            End If

        ElseIf ShouldExist And Not DoesExist Then
            '' Create Account

            ' Set objectClasses
            Dim oc As ValueCollection
            oc = Utils.ValueCollection("inetOrgPerson")
            oc.Add("person")
            oc.Add("organizationalPerson")
            oc.Add("inetOrgPerson")
            oc.Add("posixAccount")
            oc.Add("shadowAccount")
            oc.Add("simpleSecurityObject")
            CSEntry = OLMA.Connectors.StartNewConnector("posixAccount", oc)
            CSEntry.DN = expectedDN

            ' Find next available uidNumber
            CSEntry("uidNumber").Value = = NextUidNumber(OL_SERVER, OL_ROOT).ToString

            ' Set initial password
            CSEntry("userPassword").Value = "{MD5}" &amp; GenerateHash("INitial001")

            ' The following could also be done as export flow rules
            CSEntry("gidNumber").Value = OL_DEFAULT_GROUP
            CSEntry("sn").Value = mventry("lastName").Value
            CSEntry("givenName").Value = mventry("firstName").Value
            CSEntry("cn").Value = mventry("accountName").Value.ToLower
            CSEntry("uid").Value = mventry("accountName").Value.ToLower
            CSEntry("displayName").Value = mventry("firstName").StringValue &amp; " " &amp; mventry("lastName").StringValue.ToUpper

            CSEntry.CommitNewConnector()

        ElseIf Not ShouldExist And DoesExist Then
            '' Delete
            CSEntry = OLMA.Connectors.ByIndex(0)
            CSEntry.Deprovision()

        End If
    End Sub

    Public Function NextUidNumber(ByVal ldapServerName As String, ByVal searchRoot As String) As Integer
    '' The first time the function runs it finds the highest uidNumber in LDAP and stores it in a text file.
    '' On subsequent runs it retrieves the last uidNumber straight from the text file. This allows for sync runs
    '' where multiple accounts are created.
    '' The text file is deleted as part of the Terminate routine.

        Dim oSearcher As New DirectorySearcher
        Dim oResults As SearchResultCollection
        Dim oResult As SearchResult
        Dim lastuid As Integer = 0

        If File.Exists(UIDFILE) Then
            Dim fileReader As New StreamReader(UIDFILE)
            lastuid = CInt(fileReader.ReadLine)
            fileReader.Close()
        Else
            Try
                With oSearcher
                    .SearchRoot = New DirectoryEntry("LDAP://" &amp; ldapServerName &amp; "/" &amp; searchRoot, _
                                                     "cn=root,dc=ldap,dc=eesp,dc=ch", "hesso_2010", AuthenticationTypes.FastBind)
                    .PropertiesToLoad.Add("uidNumber")
                    .Filter = "uidNumber=*"
                    oResults = .FindAll()
                End With

                For Each oResult In oResults
                    If oResult.GetDirectoryEntry().Properties("uidNumber").Value &gt; lastuid Then
                        lastuid = oResult.GetDirectoryEntry().Properties("uidNumber").Value
                    End If
                Next

            Catch e As Exception
                Throw New UnexpectedDataException(e.Message)
                Return -1
            End Try
        End If

        Dim fileWriter As New StreamWriter(UIDFILE, False)
        fileWriter.WriteLine(lastuid + 1)
        fileWriter.Close()

        Return lastuid + 1
    End Function

    Private Function GenerateHash(ByVal SourceText As String) As String
        ' Returns the MD5 hash of the SourceText string.
        Dim Ue As New UTF7Encoding()
        Dim ByteSourceText() As Byte = Ue.GetBytes(SourceText)
        Dim Md5 As New MD5CryptoServiceProvider()
        Dim ByteHash() As Byte = Md5.ComputeHash(ByteSourceText)
        Return Convert.ToBase64String(ByteHash)
    End Function

End Class</pre>
<p> </p>
<p></code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/openldap-provisioning/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Provisioning BPOS powershell commands as CS Objects</title>
		<link>http://www.wapshere.com/missmiis/provisioning-bpos-powershell-commands-as-cs-objects</link>
		<comments>http://www.wapshere.com/missmiis/provisioning-bpos-powershell-commands-as-cs-objects#comments</comments>
		<pubDate>Fri, 25 Jun 2010 05:40:27 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[BPOS]]></category>
		<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[powershell]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=852</guid>
		<description><![CDATA[Here&#8217;s something else I&#8217;ve been developing, which I think has interesting potential. Essentially it&#8217;s an XMA which is used to provision powershell &#8220;command&#8221; objects. These objects contain the name of the cmdlet and the list of arguments, and the Export step is actually running the command.
The background to this is a BPOS (Microsoft cloud services) project I&#8217;ve [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s something else I&#8217;ve been developing, which I think has interesting potential. Essentially it&#8217;s an XMA which is used to provision powershell &#8220;command&#8221; objects. These objects contain the name of the cmdlet and the list of arguments, and the Export step is actually running the command.<span id="more-852"></span></p>
<p>The background to this is a BPOS (Microsoft cloud services) project I&#8217;ve been working on. BPOS doesn&#8217;t actually give you any way to disable an account while keeping the mailbox, so I wanted to be able to reset a BPOS password as part of the deprovisioning cycle. I also wanted to do this quickly and efficiently. BPOS gives you two methods to change a password: through the admin console and through powershell. Obviously powershell is the way to go for automation. </p>
<p>At the same time I was thinking of other times where I may want to fire off a powershell cmdlet &#8211; such as when initailly activating the account, when adding BPOS services, changing a mailbox quota or when adding mailbox trustees. With this method I think I have found a nicely generalised solution. My XMA will run any powershell cmdlet and all I need to do is provision these &#8220;command&#8221; objects, based on changes in the metaverse. </p>
<h3>Some Warnings&#8230;</h3>
<p>You can run into problems with this approach where FIM Sync unprovisions the unexported command object because it thinks you don&#8217;t need it any more. I found this when I tied the provisioning of the BPOS command object to the moving of an AD account, but then exported the AD MA first. The Sync service thought the command was then not needed and removed. An extra status flag should sort this out.</p>
<h3>FIM Portal Workflow may be better</h3>
<p>The other comment is that this is a Sync Service based way of approaching this problem. A better bet, if you&#8217;re using the FIM Portal, is probably to fire the powershell cmdlets directly from the Portal as part of a workflow. Unfortunately, at the moment, this means a custom workflow, which I haven&#8217;t really gotten my head around yet.</p>
<h3>About the Export Files</h3>
<p>At the moment the XMA is writing a new delta CSV file, and adding to the existing full CSV file, during each export. At the moment I only add successful runs to the full.csv file &#8211; this means I don&#8217;t have to mess about with updating existing entries &#8211; however it can lead to weird behaviour if you had some failures and follow it by a Full Import. For safety I have the &#8220;Delta Import&#8221; step as part of my Export run profile, so it always runs straight after the export. The better way would be to use SQL tables for this exported info.</p>
<h2>Steps</h2>
<h3>Preparing the XMA</h3>
<p>The server prerequisites are covered in <a href="http://www.wapshere.com/missmiis/running-remote-powershell-scripts-from-vb-net">this post</a>. You should also review <a href="http://www.wapshere.com/missmiis/remote-powershell-script-xma">this other post</a> about creating a remote powershell XMA. The big difference with this MA is that it is Call-based, giving me the ExportEntry Sub which runs against each individual export.</p>
<p>I still need a CSV file to feed to the MA creation wizard for its schema. My CSV, very simply, contains the following:</p>
<p>     cmdlet,arguments,date,time,identity,status</p>
<p>The &#8220;identity&#8221; here is an identifier for the BPOS object that I&#8217;m going to run the command against, and is not actually a unique identifier for the command itself. Because I don&#8217;t have a single unique attribute I use the following combination as the anchor: <em>cmdlet + identity + date</em>.</p>
<p>This has the useful side-effect of ensuring only one command object for a particular activity will be configured per day &#8211; though if the export of the command were to repeatedly fail you would find a new command object being provisioned the next day. Just something to watch out for.</p>
<h3>Retry Mechanism</h3>
<p>I also configure one direct Export Attribute Flow:      Constant &#8220;success&#8221; &#8211;&gt; status</p>
<p>This is my feedback mechanism. I force the export of &#8220;success&#8221; to my command object &#8211; but if the running of the command actually failed I write &#8220;failed&#8221; into the delta import file &#8211; so this actually makes a re-export happen. Until the Sync service can export and then re-import a status of &#8220;success&#8221; it will keep trying.</p>
<h3>CS Extension</h3>
<p>Here&#8217;s the CSExtension. Because this is for BPOS I&#8217;ve added the appropriate PSSnapin in the BeginExport sub. You may not need to add any snapins. Or you may just need the standard Exchange ones in which case it looks like you don&#8217;t need to add a snapin at all, because you have a nice Exchange-specific shell URI to use. There are plenty of other posts about that so I won&#8217;t say more.  <br />
  </p>
<pre>Imports Microsoft.MetadirectoryServices
Imports System.Management.Automation
Imports System.Management.Automation.Host
Imports System.Management.Automation.Runspaces
Imports System.IO

Public Class MACallExport
    Implements IMAExtensibleFileImport
    Implements IMAExtensibleCallExport

    Const SHELL_URI As String = "http://schemas.microsoft.com/powershell/Microsoft.Powershell"
    Const MA_FOLDER As String = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\MaData\BPOS commands\"
    Const DELTA_FILE As String = "delta.csv"
    Const FULL_FILE As String = "full.csv"
    Const HEADER As String = "cmdlet,arguments,date,time,identity,status"

    Dim myRunSpace As Runspace
    Dim bposCred As PSCredential

    Dim fileDelta As StreamWriter
    Dim fileFull As StreamWriter

    Public Sub GenerateImportFile(ByVal filename As String, ByVal connectTo As String, ByVal user As String, ByVal password As String, ByVal configParameters As ConfigParameterCollection, ByVal fullImport As Boolean, ByVal types As TypeDescriptionCollection, ByRef customData As String) Implements IMAExtensibleFileImport.GenerateImportFile
        ' TODO: Remove this throw statement if you implement this method
        Throw New EntryPointNotImplementedException
    End Sub

    Public Sub BeginExport(ByVal connectTo As String, ByVal user As String, ByVal password As String, ByVal configParameters As ConfigParameterCollection, ByVal types As TypeDescriptionCollection) Implements IMAExtensibleCallExport.BeginExport
        ' Start new DELTA file.
        fileDelta = New StreamWriter(MA_FOLDER &amp; DELTA_FILE, False, System.Text.Encoding.Default)
        fileDelta.WriteLine(HEADER)
        fileDelta.Close()

        ' Open existing FULL file
        fileFull = New StreamWriter(MA_FOLDER &amp; FULL_FILE, True, System.Text.Encoding.Default)

        ' Create credential for attaching to remote server
        Dim remotepass As New Security.SecureString
        Dim c As Char
        For Each c In password
            remotepass.AppendChar(c)
        Next
        Dim remoteCred As New PSCredential(user, remotepass)

        ' Create credential for attaching to BPOS
        ' The configParameters are configured on the Additional Parameters page of the MA.
        Dim bpospass As New Security.SecureString
        For Each c In configParameters("bposPassword").Value
            bpospass.AppendChar(c)
        Next
        bposCred = New PSCredential(configParameters("bposUser").Value, bpospass)

        ' Open remote powershell session
        Dim serverUri As New Uri("http://" &amp; connectTo &amp; ":5985/wsman")
        Dim connectionInfo As New WSManConnectionInfo(serverUri, SHELL_URI, remotecred)
        myRunSpace = RunspaceFactory.CreateRunspace(connectionInfo)
        Dim psException As PSSnapInException = Nothing
        myRunSpace.Open()

        Dim psh As PowerShell = PowerShell.Create()
        psh.Runspace = myRunSpace
        psh.AddCommand("Add-PSSnapin")
        psh.AddParameter("Name", "Microsoft.Exchange.Transporter")
        psh.Invoke()

    End Sub

    Public Sub ExportEntry(ByVal modificationType As ModificationType, ByVal changedAttributes As String(), ByVal csentry As CSEntry) Implements IMAExtensibleCallExport.ExportEntry
        Dim psh As PowerShell = PowerShell.Create()
        Dim psresult As New System.Collections.ObjectModel.Collection(Of PSObject)
        psh.Runspace = myRunSpace

        psh.AddCommand(csentry("cmdlet").StringValue)

        Dim arguments As String = csentry("arguments").StringValue
        If Not arguments.StartsWith(" ") Then arguments = " " &amp; arguments

        Dim parameter As String
        Dim seperator As String() = {" -"}
        For Each parameter In arguments.Split(seperator, StringSplitOptions.None)
            If parameter.Contains(" ") Then
                If parameter.Contains("$true") Then
                    psh.AddParameter(parameter.Split(" ".ToCharArray, 2)(0), True)
                ElseIf parameter.Contains("$false") Then
                    psh.AddParameter(parameter.Split(" ".ToCharArray, 2)(0), False)
                Else
                    psh.AddParameter(parameter.Split(" ".ToCharArray, 2)(0), parameter.Split(" ".ToCharArray, 2)(1))
                End If
            End If
        Next
        psh.AddParameter("Credential", bposCred)

        Dim strLine As String = csentry("cmdlet").StringValue &amp; "," &amp; csentry("arguments").StringValue &amp; "," _
                                &amp; csentry("date").StringValue &amp; "," &amp; csentry("time").StringValue &amp; "," &amp; csentry("identity").StringValue
        fileDelta = New StreamWriter(MA_FOLDER &amp; DELTA_FILE, True, System.Text.Encoding.Default)

        Try
            psresult = psh.Invoke()
        Catch ex As Exception
            fileDelta.WriteLine(strLine &amp; ",error: " &amp; ex.Message)
        End Try

        If psh.Streams.Warning.Count &gt; 0 Then
            fileDelta.WriteLine(strLine &amp; ",warning: " &amp; psh.Streams.Warning.Item(0).Message)
       ElseIf psh.Streams.Error.Count &gt; 0 Then
            fileDelta.WriteLine(strLine &amp; ",error: " &amp; psh.Streams.Error.Item(0).ErrorDetails.Message)
       Else
            fileDelta.WriteLine(strLine &amp; ",success")
            fileFull.WriteLine(strLine &amp; ",success")
        End If
        fileDelta.Close()

        psh.Dispose()
    End Sub

    Public Sub EndExport() Implements IMAExtensibleCallExport.EndExport

        fileFull.Close()
        myRunSpace.Close()

    End Sub

End Class</pre>
<p> </p>
<h3>Provisioning Code</h3>
<p>Here&#8217;s an example of provisioning a command object from the MVExtension code.</p>
<pre><code>
' To change BPOS password, provision a BPOS command object
Dim bposCmd As CSEntry
bposCmd = mventry.ConnectedMAs(MAName_BPOScmd).Connectors.StartNewConnector("command")
bposCmd("cmdlet").StringValue = "Set-MSOnlineUserPassword"
bposCmd("identity").StringValue = mventry("bposIdentity").StringValue
bposCmd("date").StringValue = Now.Date.ToString("d")
bposCmd("time").StringValue = Now.TimeOfDay.ToString
Dim randompassword As String = <em>&lt; your random password generator here &gt;</em>
bposCmd("arguments").StringValue = "-Identity " &amp; mventry("bposIdentity").StringValue &amp; " -Password " &amp; randompassword &amp; " -ChangePasswordOnNextLogon $false"
Try
     bposCmd.CommitNewConnector()
Catch ex As ObjectAlreadyExistsException
End Try
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/provisioning-bpos-powershell-commands-as-cs-objects/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Phase One Joins and Data Matching</title>
		<link>http://www.wapshere.com/missmiis/phase-one-joins-and-data-matching</link>
		<comments>http://www.wapshere.com/missmiis/phase-one-joins-and-data-matching#comments</comments>
		<pubDate>Thu, 03 Jun 2010 12:03:32 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=827</guid>
		<description><![CDATA[I&#8217;ve just posted a new Greatest Hits article on the ILM forum on the subject of how ILM (or the FIM Sync Service) can be used to clean up the mess of existing accounts, before you can actually get  on to the more interesting tasks of provisioning and updating. With the way FIM codeless sync works, [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve just posted a <a href="http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/be607357-2e40-4161-af53-83f3f59c5a54">new Greatest Hits article</a> on the ILM forum on the subject of how ILM (or the FIM Sync Service) can be used to clean up the mess of existing accounts, before you can actually get  on to the more interesting tasks of provisioning and updating. With the way FIM codeless sync works, needing an existing attribute to match on, and only allowing simple matching rules, it will be more important than ever to start from a position of tidy directories with correctly identified existsing accounts. Here&#8217;s the article&#8230;<span id="more-827"></span> </p>
<h2>Phase One Joins and Data Matching</h2>
<p>The tedious truth is that most IdM projects must begin with a phase of data matching and cleaning. Before you can start to automate management of identities, you need a predictable<br />
data set around which to base your rules. Many organizations today, whether due to changes in IT personnel, company mergers, variable naming conventions or lack of guidance on handling resignations, have existing user account bases that can only be described as a mess.</p>
<p>This document covers some of the methods you can use with ILM to get through that first project phase. Unfortunately there are no magic bullets here – eye-straining, brain-numbing trawls through long lists of unmatched accounts cannot usually be avoided. What you can do is try to extract quality data, and construct your lists as helpfully as possible, so they can be targeted at the people whose eyeballs and brains are most likely to give the best response.</p>
<p>It must be added that this is a very big topic, and the methods you choose will depend on the data you are faced with, and the aims of your project.</p>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Joins</h2>
<p>Really, it’s all about joins.</p>
<p>Once existing accounts are joined to their correct data source (such as matching an HR record to an AD account) you can begin to flow updates.</p>
<p>Once you have a clear idea who does and does not have an account in a target directory, you can begin to make provisioning and deprovisioning decisions.</p>
<div>But be careful – your joins must be reliable! The last thing you want is to kill the credibility of your fledgling IdM project by updating someone’s account with another person’s details or, even worse, deleting their account because you thought they’d left! </div>
<div><strong> </strong></div>
<p><strong> </p>
<p></strong></p>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Your eventual aim is a simple Join Rule</h3>
<p>When you first start data matching, you will use a lot of different join rules, and you will make joins manually and with CSV files (more on that below). But keep<br />
this in mind: </p>
<p><strong>Any join made manually, or with extra effort, should be considered temporary.</strong> </p>
<p>There are various situations in ILM which can only be reliably rectified by a clear-out and re-import of a connector space.</p>
<p>Always plan for this.</p>
<p>Ideally, when you re-import a connector space, you will have a single, direct join rule which effortlessly re-joins all your objects. And to achieve this we use&#8230; </p>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Breadcrumbing</h3>
<p>Once the join is verified you should export a uniquely identifying attribute, such as an employee number, to the target directory.</p>
<p>After that a simple “<em>employeeID = employeeID</em>” type join rule is all you will need. </p>
<p><img src="http://public.bay.livefilestore.com/y1puu1gB56bAwNV0j6l5x2arQF_QoG9rXeWeWhfCwNo7IE_bMzlEmKJickwi1p_DwfxfQ5tMoc61tlLdl-OQ5CZow/CW01.png" alt="" width="602" height="150" /> </p>
<p>Sometimes you are faced with an import-only system. For either technical or political reasons you are not able to export the breadcrumb attribute.</p>
<p>There are a couple of things you can do: </p>
<ol>
<li> Import the DN or other identifying attribute from the target directory into an attribute on the Metaverse object.<strong>But be warned</strong>, you could still lose these joins if you had to repopulate your Metaverse.</li>
<li> To be on the safe side you should also “save” these joins by exporting an identifying pair somewhere else – for example using a database or text file<br />
MA, as pictured below.</li>
</ol>
<p><img src="http://public.bay.livefilestore.com/y1pvIj0ZbLAvgRj_vd6hwUtb7dq1sv3Qd9Q0OraE_hLtCfEzrNaDwcbf7LaQIYkELBIocV0-A0ZP211HvM092E6WA/CW02.png" alt="" width="602" height="208" /> </p>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Understanding Join Rules</h3>
<p>There are some key points to note about Join Rules: </p>
<ol>
<li>Join rules operate on the CS object. It takes one CS object and attempts to find a match among all Metaverse objects of the correct type, even if they are already joined.</li>
<li>Components within one rule are AND-ed together – they must all match.</li>
<li>Multiple rules are evaluated top down, so put your strongest rules at the top.</li>
<li>While you can offer variations on a CS objects attribute using an Advanced Join Rule, you have to find an exact match with an attribute already on a Metaverse<br />
object. There are no “<em>StartsWith</em>” or “<em>Contains</em>” comparisons.</li>
</ol>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Resolve Join</h2>
<p>At the bottom of the Join Rule configuration you will see a check box “<em>Use rules extension to resolve</em>”.</p>
<p>Here you can link to code you write under the <em>ResolveJoinSearch</em> subroutine in the MA extension.</p>
<p>The Resolve rule is used when ILM finds multiple possible matches in the Metaverve (including already-joined objects).</p>
<p>All the possible matches into the collection rgmventry and your job, in the code, is to check through them, looking for the best possible match.</p>
<p>If your code finds an ideal match you return the index number of the object in imventry, and set the value of ResolveJoinSearch to true.</p>
<p>The following example only joins if a single, unjoined Metaverse object was found. </p>
<div style="background-color: #e6e6e6; width: 620px; overflow: scroll;">
<pre style="background-color: #e6e6e6; font-family: 'Courier New'; font-size: 11px;" lang="x-js">Public Function ResolveJoinSearch(ByVal joinCriteriaName As String, ByVal csentry As CSEntry, ByVal rgmventry() As MVEntry, ByRef imventry As Integer, ByRef MVObjectType As String) As Boolean Implements IMASynchronization.ResolveJoinSearch
<span lang="en-us">   </span>Select Case joinCriteriaName
<span lang="en-us">      </span>Case "Resolve_NotYetJoined"
<span lang="en-us">         </span>If rgmventry.Length = 1 AndAlso _
<span lang="en-us">   </span> <span lang="en-us">     </span> <span lang="en-us">  </span>rgmventry(0).ConnectedMAs("AD").Connectors.Count = 0  Then
<span lang="en-us">      </span> <span lang="en-us">  </span> <span lang="en-us">  </span>imventry = 0
<span lang="en-us">            </span>Return True
<span lang="en-us">         </span>Else
<span lang="en-us">            </span>Return False
<span lang="en-us">         </span>End If
<span lang="en-us">   </span>End Select
End Function</pre>
</div>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Advanced Join Rules</h3>
<p>A simple join rule directly matches a connector space attribute to a Metaverse attribute:</p>
<table style="border-collapse: collapse;" border="0">
<tbody>
<tr>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">Connector Space</th>
<th style="text-align: center; background-color: #d4d0c8; width: 15px; font-size: 11px; font-weight: bold;"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">Metaverse</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">givenName = Kathryn</td>
<td style="width: 15px;"> </td>
<td class="style1" style="font-size: 11px;">FirstName = Kathryn</td>
<td width="5"> </td>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">sn = Bigalow</td>
<td style="width: 15px;"> </td>
<td class="style1" style="font-size: 11px;">Lastname = Bigalow</td>
<td width="5"> </td>
</tr>
</tbody>
</table>
<p>With an Advanced Join Rule you construct a list of possible values with which to find an exact match in the Metaverse:</p>
<table style="border-collapse: collapse;" border="0">
<tbody>
<tr>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">Connector Space</th>
<th style="text-align: center; background-color: #d4d0c8; width: 15px; font-size: 11px; font-weight: bold;"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">Metaverse</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">givenName = KathryngivenName = KategivenName = Kathy</td>
<td style="width: 15px;"> </td>
<td class="style1" style="font-size: 11px;" valign="top">FirstName = Kate</td>
<td width="5"> </td>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">sn = Bigalow</td>
<td style="width: 15px;"> </td>
<td class="style1" style="font-size: 11px;" valign="top">Lastname = Bigalow</td>
<td width="5"> </td>
</tr>
</tbody>
</table>
<p>Sometimes you can use code rules to make your list of possible matches (eg., presenting a phone number in different formats); other times you have to use long<br />
look-up lists of possible variations (try genealogy websites for name-variation lists).</p>
<p>The following example uses a lookup file of aliases, where each line has the possible variations on a name.</p>
<p>If a match to the first name is found on the connector space object, the whole line is added to the possible values to search for in the Metaverse. </p>
<p>Elizabeth,Liz,Beth,Betty<br />
David,Dave,Davey<br />
Jerome,Jérôme<br />
&#8230; </p>
<div style="background-color: #e6e6e6; width: 620px; overflow: scroll;">
<pre style="background-color: #e6e6e6; font-family: 'Courier New'; font-size: 11px;" lang="x-js">Public Class MAExtensionObject
  <span lang="en-us"> </span>Implements IMASynchronization
  <span lang="en-us"> </span>Dim fileAliases As System.IO.StreamReader
  <span lang="en-us"> </span>Dim arrAliases As String()
  <span lang="en-us"> </span>Dim i As Integer

  <span lang="en-us"> </span>Public<span lang="en-us"> </span>Sub Initialize() Implements IMASynchronization.Initialize
    <span lang="en-us">  </span>fileAliases = New System.IO.StreamReader("C:\aliases.txt",  System.Text.Encoding.Default)
    <span lang="en-us">  </span>i = 0
    <span lang="en-us">  </span>While Not fileAliases.EndOfStream
      <span lang="en-us">   </span>ReDim Preserve arrAliases(i)
      <span lang="en-us">   </span>arrAliases(i) = fileAliases.ReadLine
      <span lang="en-us">   </span>i = i + 1
    <span lang="en-us">  </span>End While

    <span lang="en-us"> </span>fileAliases.Close()
  <span lang="en-us"> </span>End Sub

<span lang="en-us">   </span>Public Sub MapAttributesForJoin(ByVal FlowRuleName As String, ByVal csentry As CSEntry, ByRef values As ValueCollection) Implements IMASynchronization.MapAttributesForJoin
   <span lang="en-us">   </span>Select FlowRuleName
      <span lang="en-us">   </span>Case "Join_aliases"
         <span lang="en-us">   </span>Dim aliasList As String
         <span lang="en-us">   </span>Dim value As String

         <span lang="en-us">   </span>For Each aliasList In arrAliases
            <span lang="en-us">   </span>If aliasList.Contains(csentry("givenName").Value) Then
               <span lang="en-us">   </span>For Each value In aliasList.Split(",".ToCharArray)
                 <span lang="en-us"> </span> <span lang="en-us">  </span>values.Add(value)
               <span lang="en-us">   </span>Next
	<span lang="en-us">      </span>End If
         <span lang="en-us">   </span>Next
    <span lang="en-us">     </span>End Select
  <span lang="en-us">   </span>End Sub
End Class</pre>
</div>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Using CSV Files</h2>
<p>A lot of the difficult account matching will probably be done outside ILM.</p>
<p>It is therefore useful to be able to “export” lists of possible matches, and later, “import” the joins from a CSV file.</p>
<p>Be careful when writing to files from extension code – the DLL doesn’t unload for five minutes after the MA run completes, which means you may have to wait for it<br />
to finish writing to the file. If you’re in a hurry, recompiling the code will force the DLL to finish writing to the file. </p>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Export Possible Matches to a CSV file</h3>
<p>You can use the Resolve rule to export possible matches to a CSV file. The following example resolves the join rule “sn | Direct | lastname”. As a match on the last<br />
name alone is too weak for an immediate join, we just write the possible matches to the text file. </p>
<div style="background-color: #e6e6e6; width: 620px; overflow: scroll;">
<pre style="background-color: #e6e6e6; font-family: 'Courier New'; font-size: 11px;" lang="x-js">Public Class MAExtensionObject<span lang="en-us"> </span>Implements IMASynchronization
  <span lang="en-us"> </span>Dim fileMatches As System.IO.StreamWriter

<span lang="en-us"> <span class="style1">  </span></span>Public<span lang="en-us"> </span>Sub Initialize() Implements IMASynchronization.Initialize
    <span lang="en-us">  </span>fileMatches = New System.IO.StreamWriter("C:\possible matches.txt", System.Text.Encoding.Default)
  <span lang="en-us"> </span>End Sub

  <span lang="en-us"> </span>Public Sub Terminate() Implements IMASynchronization.Terminate
    <span lang="en-us">  </span>fileMatches.Close()
  <span lang="en-us"> </span>End Sub

<span lang="en-us">   </span>Public Function ResolveJoinSearch(ByVal joinCriteriaName As String, ByVal csentry As CSEntry, ByVal rgmventry() As MVEntry, ByRef imventry As Integer, ByRef MVObjectType As String) As Boolean Implements IMASynchronization.ResolveJoinSearch
  <span lang="en-us">    </span>Select Case joinCriteriaName
      <span lang="en-us"> <span class="style1">  </span></span>Case "Resolve_Lastname"
         <span lang="en-us">   </span>Dim MAName As String = csentry.MA.Name
         <span lang="en-us">   </span>Dim mvobject As MVEntry
         <span lang="en-us">   </span>Dim cFirstname, mFirstname As String

         <span lang="en-us">   </span>If csentry("givenName").IsPresent Then
     <span lang="en-us">          </span>cFirstname = csentry("givenName").StringValue
   <span lang="en-us">         </span>Else
     <span lang="en-us">       </span> <span lang="en-us">  </span>cFirstname = "UNKNOWN"
         <span lang="en-us">   </span>End If

         <span lang="en-us">   </span>If mvobject("firstname").IsPresent Then
     <span lang="en-us">       </span> <span lang="en-us">  </span>mFirstname = mvobject("firstname").StringValue
   <span lang="en-us">         </span>Else
     <span lang="en-us">       </span> <span lang="en-us">  </span>mFirstname = "UNKNOWN"
         <span lang="en-us">   </span>End If

         <span lang="en-us">   </span>For Each mvobject In rgmventry
           <span lang="en-us">    </span>If mvobject.ConnectedMAs(MAName).Connectors.Count = 0  Then
            <span lang="en-us"> <span class="style1">  </span></span> <span lang="en-us">  </span>fileMatches.WriteLine(csentry("sn").StringValue <span lang="en-us">&amp;</span> ";" _
     		<span lang="en-us">   &amp;</span> cFirstname <span lang="en-us">&amp;</span> ";" _
     		<span lang="en-us">   &amp;</span> mvobject("lastname").StringValue <span lang="en-us">&amp;</span> ";" _
     		<span lang="en-us">   &amp;</span> mFirstname)
           <span lang="en-us">    </span>End If
         <span lang="en-us">   </span>Next
         <span lang="en-us">   </span>Return False
<span lang="en-us">         </span>End Select
<span lang="en-us"> <span class="style1">  </span></span>End Function
<span lang="en-us">End Class</span></pre>
</div>
<p>You will need to adapt this code of course. Firstly, you probably want to export a lot more identifying information in your CSV file – department, email address,<br />
dn &#8230; whatever helps. Next, it can really help to supplement your possible matches with a probability score. This is where you do a series of tests and add points-<br />
the more points, the higher the chance of the match. For example: </p>
<ul>
<li>Names similar*          +1</li>
<li>Department the same +1</li>
<li>City the same           <br />
+1</li>
</ul>
<p>*Some tips for testing if names are similar: </p>
<ul>
<li>Strip out all spaces, dashes and hyphens then compare;</li>
<li>Check if one string is contained in the other (so that “Sally-Anne” gets<br />
a point for “Sally”);</li>
<li>Use a function which compares string similarity (search “Soundex” and “Levenshtein<br />
distance”).</li>
</ul>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Join from CSV</h2>
<p>You can use an Advanced Join Rule to “<em>import</em>” joins from a CSV file.</p>
<p>Firstly, our CSV file must be constructed like this: </p>
<p>CS_identifier;MV_identifier </p>
<p>For example, if we are trying to match an AD account against Metaverse objects imported from the HR system, we populate the CSV with the AD DN and the employeeID: </p>
<p>CN=Fred Bloggs,OU=User,OU=MyOrg,DC=mydomain,DC=com;0012988 </p>
<p>Next we create the Advanced Join Rule which will look up the csobject’s DN in the text file, but use the employeeID to search the Metaverse. </p>
<p>Note that you can’t actually use the DN in the join rule – but that’s ok, just use any attribute that definitely exists. Eg., </p>
<p>sAMAccountName | Rules extension – Join_CSV | employeeID </p>
<p>And now for the code : </p>
<div style="background-color: #e6e6e6; width: 620px; overflow: scroll;">
<pre style="background-color: #e6e6e6; font-family: 'Courier New'; font-size: 11px;" lang="x-js">Imports Microsoft.MetadirectoryServices

Public Class MAExtensionObject<span lang="en-us"> </span>Implements IMASynchronization
  <span lang="en-us"> </span>Dim joins As String()
  <span lang="en-us"> </span>Dim i As Integer

<span lang="en-us">   </span>Public<span lang="en-us"> </span>Sub Initialize() Implements IMASynchronization.Initialize
   <span lang="en-us">   </span>Dim fileJoins As System.IO.StreamReader
   <span lang="en-us">   </span>Dim strLine As String
   <span lang="en-us">  </span>'Open the csv file and read into an array
   <span lang="en-us">   </span>fileJoins = New System.IO.StreamReader("C:\joins.csv", System.Text.Encoding.Default)
   <span lang="en-us">   </span>i = 0
   <span lang="en-us">   </span>While Not fileJoins.EndOfStream
     <span lang="en-us"> </span> <span lang="en-us">  </span>ReDim Preserve joins(i)
     <span lang="en-us">    </span>joins(i) = fileJoins.ReadLine
      <span lang="en-us">   </span>i = i + 1
   <span lang="en-us">   </span>End While
   <span lang="en-us">   </span>fileJoins.Close()
<span lang="en-us">   </span>End Sub

<span lang="en-us">   </span>Public Sub MapAttributesForJoin(ByVal FlowRuleName As String, ByVal csentry As CSEntry, ByRef values As ValueCollection) Implements IMASynchronization.MapAttributesForJoin
   <span lang="en-us">   </span>Select FlowRuleName
      <span lang="en-us">   </span>Case "Join_CSV"
  <span lang="en-us">          </span>'If the csentry DN is found in the joins array, then
  <span lang="en-us">          </span>'use the paired employeeID to search the Metaverse.
        <span lang="en-us">    </span>For i = 0 To joins.Length - 1
          <span lang="en-us">     </span>If joins(i).Contains(csentry.DN.ToString) Then
            <span lang="en-us">   </span> <span lang="en-us">  </span>values.Add(joins(i).Split(";")(1))
          <span lang="en-us">     </span>End If
        <span lang="en-us"> </span>Next

   End Select
End Sub</pre>
</div>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Reporting</h2>
<p>People will ask you questions like “How many people have you joined in system X but not in Y?”, “How sure are you that the joins are correct?”, “Which department<br />
has the most unidentified accounts?” It’s best to be prepared for these sorts of questions. </p>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Querying the Metaverse</h3>
<p>Once data is in the Metaverse it is a simple matter to access it for reporting, either by exporting it into a reporting table, or by directly querying the underlying<br />
tables (as long as you’re careful to do it when ILM is idle, or else use NOLOCK). </p>
<p>So consider this: <strong>During the data cleaning phase, import all identifying attributes into the Metaverse from all sources</strong>. </p>
<p>For example: You’ve made a join between a user in AD and an HR record.</p>
<p>Under normal operations you would consider HR as the master source for the name, and you would only flow it from there.</p>
<p>You wouldn’t bother importing the name attributes from AD – in fact you’re more likely to be overwriting them with export flow rules.</p>
<table style="border-collapse: collapse;" border="0">
<tbody>
<tr>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">HR</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">Metaverse</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">AD</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">Lastname = Powells-Brown</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">-&gt;</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">lastname = Powells-Brown</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">-&gt;</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">sn = Powells-Brown</td>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">Firstname = Joanna</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">-&gt;</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">firstname = Joanna</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">-&gt;</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">givenName = Joanna</td>
<td width="5"> </td>
</tr>
</tbody>
</table>
<p>However, during the data matching phase, you’re probably not ready to start overwriting attributes, and the information about current values can be very important in your<br />
verification and reporting.</p>
<table border="0">
<tbody>
<tr>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">HR</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">Metaverse</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;">AD</th>
<th style="background-color: #d4d0c8; text-align: center; font-weight: bold; font-size: 11px;" width="5"> </th>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">Lastname = Powells-Brown</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">-&gt;</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">HR_lastname = Powells-Brown</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">-&gt;</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">sn = Powells-Brown</td>
<td width="5"> </td>
</tr>
<tr>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">Firstname = Joanna</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">-&gt;</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">HR_firstname = Joanna<br />
AD_lastname = Powells<br />
AD_firstname = Jo</td>
<td width="5"> </td>
<td style="text-align: center; font-size: 11px;">&lt;-<br />
&lt;-</td>
<td width="5"> </td>
<td class="style1" style="font-size: 11px;">givenName = Joanna<br />
givenName = Jo</td>
<td width="5"> </td>
</tr>
</tbody>
</table>
<p>Now, if you have a look at the <em>mms_metaverse</em> table in the ILM database, you will see how simple it is to query the progress of your joins, and also to judge<br />
on what criteria the joins were made.</p>
<p>Some example queries… </p>
<p>/* HR person with no join to AD */</p>
<p>select HR_lastname, HR_firstname, HR_employeeid from mms_metaverse<br />
where AD_dn is null </p>
<p> /* HR person with join to AD */</p>
<p>select HR_lastname, HR_firstname, HR_employeeid, AD_lastname,AD_firstname,AD_dn from mms_metaverse<br />
where AD_dn is not null </p>
<table style="width: 100%;" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<th style="text-align: left; background-color: #c0c0c0;"><img src="http://apfhrw.bay.livefilestore.com/y1psOzY0M-yAjhcC7950DyjOEOMTO7g7UXPJsJ3MsIEAGPWZ7cLWtso19wWC_aIFviX49wqL5BR_hx5guCJhgwNCAqxNFi-2oyW/Caution.gif" alt="Caution" /><span class="style2"><strong> Caution</strong></span></th>
</tr>
<tr>
<td style="background-color: #f0f0f0;">As mentioned above you need to be<br />
careful when directly querying the Metaverse tables.If your system is already in production, and you happen to be adding in<br />
a new data source, then you may be better off employing a SQL MA to export<br />
the data you’re interested in to another table, where you can query it as<br />
much as you like without risk of locking errors.</td>
</tr>
</tbody>
</table>
<p>  </p>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Querying the Connector Space</h2>
<p>Unfortunately it is not so simple to query the connector space to, for example, report on that state of your disconnected objects.</p>
<p>It’s a great pity that you can’t just save results from the Joins page in the Identity Manager GUI, so your options are: </p>
<p><strong>SQL query</strong> &#8211; The CS table holds data differently to the Metaverse tables.</p>
<p>It is possible to query for disconnectors in this way, however you will only be able to retrieve the CN of objects – which may not be sufficient to identify them. </p>
<div style="background-color: #e6e6e6; width: 620px; overflow: scroll;">
<pre style="background-color: #e6e6e6; font-family: 'Courier New'; font-size: 11px;" lang="x-js">select cs.rdn from dbo.mms_connectorspace cs
join dbo.mms_management_agent ma
on cs.ma_id = ma.ma_id
left outer join dbo.mms_csmv_link mv
on mv.cs_object_id = cs.object_id
where ma.ma_name = 'My MA'
and mv.mv_object_id is null
and cs.connector_state = 0</pre>
</div>
<p><strong>CSExport</strong> &#8211; The command-line utility csexport.exe, found in the &lt;ILM program&gt;\bin folder, will allow you to dump connector space objects to an XML file. </p>
<p><strong>Report directly from the data source</strong> &#8211; Once an object in the data source has been correctly identified you will ideally export a unique attribute out to it. It may then be possible to identify the non-joined objects as those which don’t possess this attribute. </p>
<p><strong>Project to a different object type</strong> &#8211; ILM processes joins before projections, so it is fairly simple to project all non-joined objects to a different Metaverse object type – for example, one called “disconnectors”. This may help in reporting on an overall status direct from the Metaverse tables.</p>
<p>Note however that to make these objects once again available for joins they will have to be disconnected from the MVExtension provisioning code. </p>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Advanced Tips and Tricks</h2>
<p style="color: #365f91; font-size: 14px; font-weight: bold;">Join to Multi-value Attribute</p>
<p>Sometimes you may need to join to a value in a multi-value attribute. An example is searching through all the <em>proxyAddresses</em> for a match against<br />
a single email address. </p>
<p>When the multi-valued attribute exists in the connector space, and the single valued attribute is in the Metaverse, this is very easily accomplished. Just use an Advanced Join Rule to break the multi-valued attribute down into the values list used by the join rule. </p>
<div style="background-color: #e6e6e6; width: 620px; overflow: scroll;">
<pre style="background-color: #e6e6e6; font-family: 'Courier New'; font-size: 11px;" lang="x-js"> Public Sub MapAttributesForJoin(ByVal FlowRuleName As String, ByVal csentry As CSEntry, ByRef values As ValueCollection) Implements IMASynchronization.MapAttributesForJoin
<span lang="en-us">   </span>Select FlowRuleName
<span lang="en-us">      </span>Case "Join_proxyAddresses"
<span lang="en-us">         </span>Dim alias As String
<span lang="en-us">         </span>For Each alias In csentry("givenName").Values
<span lang="en-us">   </span> <span lang="en-us">  </span> <span lang="en-us">  </span> <span lang="en-us">  </span>values.Add(alias)
<span lang="en-us">         </span>Next
<span lang="en-us">   </span>End Select
End Sub</pre>
</div>
<p>However, when the multi-valued attribute has already been imported into the Metaverse you will have a problem. While you can join on a multi-valued attribute, you have to join on the whole thing. There is no way that you can match one value out of a multi-valued Metaverse attribute against a single-valued connector space attribute. </p>
<p>Some possible options: </p>
<ul>
<li>Import the multi-valued attribute into a series of single-valued attributes in the Metaverse, eg., proxy1, proxy2, proxy3 …</li>
<li>Use a different Metaverse object type to do the project and join the other way around.</li>
</ul>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Multiple Possibilities in the Connector Space</h3>
<p>All the joining techniques so far have been based around a single connector space object, with one or more possible matches in the Metaverse. But what do you do if you want to work the other way around, where there are multiple possible matches in the connector space for a single Metaverse object, and you want to pick the best one? Unfortunately this is not straight-forward. There is no way to offer a selection of connector space objects in a join rule, as joins always work on a single connector space object at a time. You can judge the merits of the current CS object, but you can’t tell if there’s a better one coming up. One way around this is to do everything with CSV files. </p>
<ol>
<li>Use the ResolveJoinSearch to write possible joins to a CSV, but don’t actually join anything,</li>
<li>Do your matching outside ILM, then</li>
<li>Use a CSV with an Advanced Join Rule to make the joins.</li>
</ol>
<h3 style="color: #365f91; font-size: 13px; font-weight: bold;">Too much data</h3>
<p>If you have an enormous number of accounts, and different data sources to trawl through, you may be best off doing your data matching outside of ILM, and then just using the CSV join rule above to make the joins.  Check out the Fuzzy Lookup Transformation from the Enterprise version of SQL SSIS for help here. </p>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Take-Home Thoughts</h2>
<ul>
<li>Complex join rules are a means to an end – not the end itself.</li>
<li>Breadcrumbing is essential for automation.</li>
<li>When matching on weak rules (eg., surname only) then verify the match another way.</li>
<li>You can’t do it all in ILM. CSV, Excel and fuzzy lookup algorithms will also help, but an element of by-hand matching is inevitable.</li>
<li>Get the matching and breadcrumbing sorted out before you start flowing and provisioning.This will make for a happier project, stake holders, users and YOU!</li>
</ul>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">Extra Reading</h2>
<ul>
<li><a href="http://technet.microsoft.com/en-us/library/cc720664(WS.10).aspx">Design Concepts for Correlating Digital Identities</a></li>
<li><a href="http://download.microsoft.com/download/1/3/7/137d2f75-f95c-4aea-b553-a311203058cc/CorrectingJoins.doc">Correcting incorrect joins</a></li>
</ul>
<h2 style="color: #365f91; font-size: 14px; font-weight: bold;">About the Author</h2>
<p>Carol Wapshere is an ILM MVP and contributor of a few of these documents now. She has always believed that putting the work in up-front will save you lots of headaches in the long run. It’s a self-defense strategy really – there’s nothing worse than having to go over and over (and over) the same ground again and again, especially when you’d already moved on to something new and far more interesting. Keep it neat, and keep it simple, and things should work just fine. </p>
<p>Thanks to Markus Vilcinskas and Paul Loonen for their help with this document.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/phase-one-joins-and-data-matching/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>ConnectionChangeTime</title>
		<link>http://www.wapshere.com/missmiis/connectionchangetime</link>
		<comments>http://www.wapshere.com/missmiis/connectionchangetime#comments</comments>
		<pubDate>Thu, 25 Feb 2010 18:25:22 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=683</guid>
		<description><![CDATA[This got me out of a pickle today. A slip-up in a join rule caused hundreds of bad joins to be made. There were far too many to un-do by hand, but then I figured out I could add a few lines to the Provisioning Sub of the MVExtension to remove all the joins made [...]]]></description>
			<content:encoded><![CDATA[<p>This got me out of a pickle today. A slip-up in a join rule caused hundreds of bad joins to be made. There were far too many to un-do by hand, but then I figured out I could add a few lines to the Provisioning Sub of the MVExtension to remove all the joins made in the past hour:<br />
<code><br />
If mventry.ConnectedMAs(<em>Ma Name</em>).Connectors.Count &gt; 0 _<br />
AndAlso DateDiff(DateInterval.Hour, mventry.ConnectedMAs(<em>Ma Name</em>).Connectors.ByIndex(0).ConnectionChangeTime, Now) &lt; 1 Then<br />
  mventry.ConnectedMAs(<em>Ma Name</em>).Connectors.ByIndex(0).Deprovision()<br />
End If</code></p>
<p>Handy!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/connectionchangetime/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

