<?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; VB.NET</title>
	<atom:link href="http://www.wapshere.com/missmiis/category/vbnet/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>Generating Reference attributes from String data</title>
		<link>http://www.wapshere.com/missmiis/generating-reference-attributes-from-string-data</link>
		<comments>http://www.wapshere.com/missmiis/generating-reference-attributes-from-string-data#comments</comments>
		<pubDate>Fri, 21 Oct 2011 23:01:08 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[VB.NET]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1743</guid>
		<description><![CDATA[Once upon a time we used to be able to write Advanced Flow Rules for reference attributes. Admittedly this sometimes led to horribly inefficient code, but it was useful &#8211; particluarly when paired with FindMVEntries to lookup and then reference an existing Metaverse object.
With FIM we lost this capability, and Microsoft claim we were never [...]]]></description>
			<content:encoded><![CDATA[<p>Once upon a time we used to be able to write Advanced Flow Rules for reference attributes. Admittedly this sometimes led to horribly inefficient code, but it was useful &#8211; particluarly when paired with FindMVEntries to lookup and then reference an existing Metaverse object.</p>
<p>With FIM we lost this capability, and Microsoft claim we were never supposed to be doing it anyway &#8211; that it was &#8220;unsupported&#8221; all along. So what do you do if you&#8217;ve got string data and you really need references? One Sync-based way is to loop the data through a SQL MA, bringing it back in as a reference.</p>
<p><span id="more-1743"></span></p>
<p>The method outlined here generates a <strong>manager</strong> attribute from the two string attributes <strong>position</strong> and <strong>superiorPosition</strong>, which hold position numbers (as distinct from employee numbers).</p>
<h3>Create SQL Tables</h3>
<p>Create the following tables:</p>
<ul>
<li>GenerateRef_Objects</li>
</ul>
<p style="padding-left: 60px;">DN [nvarchar] (200)</p>
<p style="padding-left: 60px;">objectType [nchar] (50)</p>
<ul>
<li>GenerateRef_MultiValue</li>
</ul>
<p style="padding-left: 60px;">DN [nvarchar] (200)</p>
<p style="padding-left: 60px;">attribute [nchar] (50)</p>
<p style="padding-left: 60px;">Reference [nvarchar] (200)</p>
<p>The plan for these tables is to export data to them so that the Objects table lists the possible position numbers along with an objectType of &#8220;position&#8221;, and the MultiValue table shows the relationship between the positions, with the one in the Reference column being the manager.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/sqlmv.jpg"><img class="alignnone size-full wp-image-1759" title="sqlmv" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/sqlmv.jpg" alt="" width="365" height="128" /></a></p>
<p>Note that the &#8220;x&#8221; entry is just a placeholder I put in while creating the MA, because it needs to see at least one objectType specified. Once the MA is created I can delete that line.</p>
<h3>Create a SQL MA</h3>
<p>Start by creating the SQL Management Agent in the usual way:</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA1.jpg"><img class="size-full wp-image-1747 alignnone" title="MA1" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA1.jpg" alt="" width="526" height="387" /></a></p>
<p>Set the anchor to the DN column:</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA2.jpg"><img class="alignnone size-full wp-image-1750" title="MA2" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA2.jpg" alt="" width="540" height="395" /></a></p>
<p>You will also have to configure the Multi-Value settings. These are a little obscure, and I&#8217;ve explained them in more detail <a href="http://www.wapshere.com/missmiis/configuration-of-the-sql-ma">elsewhere</a>, so I&#8217;ll just show a piccy here:</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA3.jpg"><img class="alignnone size-full wp-image-1751" title="MA3" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA3.jpg" alt="" width="326" height="377" /></a></p>
<p>As well you have to set an object type. For flexibility I&#8217;m just going to point it at my &#8220;objectType&#8221; column, meaning I could, potentially, support multiple types with this MA.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA4.jpg"><img class="alignnone size-full wp-image-1752" title="MA4" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA4.jpg" alt="" width="350" height="183" /></a></p>
<p>Set a join rule between the column where the position number is stored in the Objects table (in this case the &#8220;DN&#8221;) and the position attribute in the Metaverse.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA5.jpg"><img class="alignnone size-full wp-image-1754" title="MA5" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA5.jpg" alt="" width="520" height="387" /></a></p>
<p>Now for the flow rules: we want to flow the superiorPosition string value out to manager, and then the same value back to the manager attribute in the Metaverse &#8211; but now magically transformed to a reference.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA6.jpg"><img class="alignnone size-full wp-image-1755" title="MA6" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA6.jpg" alt="" width="526" height="387" /></a></p>
<p>Finally set your deprovisioning rules:</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA7.jpg"><img class="alignnone size-full wp-image-1756" title="MA7" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/10/MA7.jpg" alt="" width="460" height="338" /></a></p>
<h3>Metaverse Code</h3>
<p>The final step is to write the provisioning code that creates the position objects in the SQL table.</p>
<p>(As this is a Sync-based method I&#8217;m going classic with none of that &#8220;declarative&#8221; malarky. If you had the Portal in the mix you may well be sorting out this manager stuff in there anyway.)</p>
<pre>    Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision
        If mventry.ObjectType &lt;&gt; PersonObjectClass Then
            Exit Sub
        End If

        Dim DoesExist As Boolean = False
        Dim ShouldExist As Boolean = False

        If mventry.ConnectedMAs(MAName).Connectors.Count &gt; 0 Then
            DoesExist = True
        End If

        If mventry("position").IsPresent Then
            ShouldExist = True
        End If

        If ShouldExist And Not DoesExist Then
            Dim csentry As CSEntry
            csentry = mventry.ConnectedMAs(MAName).Connectors.StartNewConnector("position")
            csentry("DN").Value = mventry("position").Value
            csentry.CommitNewConnector()

        ElseIf DoesExist And Not ShouldExist Then
            Dim csentry As CSEntry
            csentry = mventry.ConnectedMAs(MAName).Connectors.ByIndex(0)
            csentry.Deprovision()
        End If
    End Sub</pre>
<h3>What should happen</h3>
<p>When you sync your person objects you should see &#8220;position&#8221; objects being provisioned. Export, Import and Sync and you should see the reference value flow back into the Metaverse.</p>
<p>Note that this method does assume position numbers are unique &#8211; if you have a possibility of duplicate position numbers (such as with a job share) then you will need to get a bit more creative.</p>
<p>If you have a lot of data you should also look at <a href="http://www.wapshere.com/missmiis/delta-and-multivalued-combined">generating a Delta table</a> for the confirming Import step.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/generating-reference-attributes-from-string-data/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to use the EmailNotificationActivity in a custom workflow</title>
		<link>http://www.wapshere.com/missmiis/how-to-use-the-emailnotificationactivity-in-a-custom-workflow</link>
		<comments>http://www.wapshere.com/missmiis/how-to-use-the-emailnotificationactivity-in-a-custom-workflow#comments</comments>
		<pubDate>Mon, 22 Aug 2011 05:06:04 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[Email Template]]></category>
		<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1638</guid>
		<description><![CDATA[I just put together a workflow prototype for a project: when the membership of certain Criteria-based groups changes the groups&#8217; Owners should be notified. This gave me an opportunity to use the EmailNotificationActivity inside a custom Workflow for the first time. As usual I found the official documentation to be somewhat &#8230; thin.

Implementing the Activity
As [...]]]></description>
			<content:encoded><![CDATA[<p>I just put together a workflow prototype for a project: when the membership of certain Criteria-based groups changes the groups&#8217; Owners should be notified. This gave me an opportunity to use the EmailNotificationActivity inside a custom Workflow for the first time. As usual I found the <a href="http://msdn.microsoft.com/en-us/library/microsoft.resourcemanagement.workflow.activities.emailnotificationactivity.aspx">official documentation</a> to be somewhat &#8230; thin.</p>
<p><span id="more-1638"></span></p>
<h3>Implementing the Activity</h3>
<p>As is the case with most of these FIM activity components, you need to set the stage with some code first.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivity.jpg"><img class="alignnone size-full wp-image-1639" title="EmailNotificationActivity" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivity.jpg" alt="" width="164" height="132" /></a></p>
<p>Inside my code activity I have to set the following two properties:</p>
<ul>
<li>the To field &#8211; a comma separated list of email addresses, and</li>
<li>the Email Template, which must exist in the FIM Portal, and is identified by its ResourceID.</li>
</ul>
<p>The following example is the most simple as I&#8217;ve just hardcoded the values.</p>
<pre>Me.emailNotificationActivity_To1 = "jack.black@somecompany.com,jack.white@somecompany.com"
Me.emailNotificationActivity_EmailTemplate1 = New System.Guid("727756dc-92b3-4861-8cab-4ab81ca28a3d")</pre>
<p>Of course you won&#8217;t be hardcoding values in your code like this &#8211; where you get them from I&#8217;ll leave up to you.</p>
<p>In my case I get the email addresses by looping through the group&#8217;s Owners. The email template I&#8217;m getting through the completely inelegant approach of hardcoding the template&#8217;s ResourceID in my Workflow configuration. <strong><em>If anyone can tell me how to get an Identity Picker into the Workflow UI I&#8217;d be much oblidged!</em></strong></p>
<p><strong><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivityUI.jpg"><img class="alignnone size-full wp-image-1640" title="EmailNotificationActivityUI" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivityUI.jpg" alt="" width="456" height="62" /></a></strong></p>
<h3>Fleshing out your email with WorkflowData</h3>
<p>The other thing I found is that I needed to use WorkflowData parameters to improve the content of my notification email.</p>
<p>For example, you don&#8217;t want to send an email to someone saying &#8220;A group has changed&#8221; &#8211; that&#8217;s not very informative! You want to send them &#8220;Group &#8217;Dark Chocolate Fans&#8217; has changed; Carol Wapshere has been added to group &#8216;Dark Chocolate Fans&#8217; &#8220;.</p>
<p>In my prototype I could get the name of the of the user whose membership had changed through the //Target construct, but I was reading the changed group (or groups) from the Request object. This meant that the Group Name was something I would only have access to while the workflow was running.</p>
<p>I added a string property to the Workflow UI where the user can enter a WorkflowData parameter name:</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivityUI-wfdata.jpg"><img class="alignnone size-full wp-image-1641" title="EmailNotificationActivityUI-wfdata" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivityUI-wfdata.jpg" alt="" width="282" height="31" /></a></p>
<p>I then made sure that I was using the same parameter name in my Email Template:</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivity-EmailTmpl.jpg"><img class="alignnone size-full wp-image-1642" title="EmailNotificationActivity-EmailTmpl" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/08/EmailNotificationActivity-EmailTmpl.jpg" alt="" width="517" height="236" /></a></p>
<p>And finally, I added this code to the initNotification activity, after setting the To and Template parameters. Note that <strong>WFParamName</strong> is the UI Property I defined. And the object <strong>readGroup_Resource1</strong> is the result of an earlier <a href="http://www.wapshere.com/missmiis/how-to-use-the-fim-readresourceactivity-in-vb-net">ReadResourceActivity</a> where I looked up the group object.</p>
<pre>            '' Return group name in WorkflowData to use in email template
            Dim containingWorkflow As SequentialWorkflow = Nothing
            If Not SequentialWorkflow.TryGetContainingWorkflow(Me, containingWorkflow) Then
                Throw New InvalidOperationException("Unable to get Containing Workflow")
            End If
            Try
                If containingWorkflow.WorkflowDictionary.ContainsKey(Me.WFParamName) Then
                    containingWorkflow.WorkflowDictionary.Remove(Me.WFParamName)
                End If
                containingWorkflow.WorkflowDictionary.Add(Me.WFParamName, readGroup_Resource1("DisplayName"))
            Catch
                Throw New InvalidOperationException("Unable to update WorkflowData parameter " &amp; Me.WFParamName)
            End Try</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/how-to-use-the-emailnotificationactivity-in-a-custom-workflow/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>BPOS Powershell Activity</title>
		<link>http://www.wapshere.com/missmiis/bpos-powershell-activity</link>
		<comments>http://www.wapshere.com/missmiis/bpos-powershell-activity#comments</comments>
		<pubDate>Tue, 03 May 2011 18:24:07 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[BPOS]]></category>
		<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1424</guid>
		<description><![CDATA[Here&#8217;s something I&#8217;ve been promising to post for a while &#8211; my BPOS Powershell Activity.

It&#8217;s designed to work with the Function Evaluator
The first point to note is that, unless you&#8217;re running a script or cmdlet that takes no parameters, you&#8217;re going to have to generate a parameter string. For BPOS this will include the BPOS [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s something I&#8217;ve been promising to post for a while &#8211; my BPOS Powershell Activity.<br />
<span id="more-1424"></span></p>
<h3>It&#8217;s designed to work with the Function Evaluator</h3>
<p>The first point to note is that, unless you&#8217;re running a script or cmdlet that takes no parameters, you&#8217;re going to have to generate a parameter string. For BPOS this will include the BPOS Identity as a minimum. I use the Function Evaluator ahead of this activity to generate my powershell parameter string, which I then pass to the powershell activity through WorkflowData.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/05/bpos-activity-funtion-evaluator.jpg"><img class="alignnone size-full wp-image-1425" title="bpos activity funtion evaluator" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/05/bpos-activity-funtion-evaluator.jpg" alt="" width="574" height="321" /></a></p>
<h3>The Parameter string must be in an expected format</h3>
<p>My activity only supports parameter strings in the format &#8220;-Param &#8216;Argument&#8217; -Param &#8216;Argument&#8217;&#8221;.</p>
<ul>
<li>Scripts using the arg(0), arg(1) method are not supported.</li>
<li>String arguments only.</li>
<li>All arguments have to be enclosed in single-quotes, even if you don&#8217;t have to do this when running the cmdlet directly in powershell.</li>
</ul>
<h3>Using the Activity</h3>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/05/bpos-activity.jpg"><img class="alignnone size-full wp-image-1426" title="bpos activity" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/05/bpos-activity.jpg" alt="" width="578" height="474" /></a><br />
<strong>General Control Settings</strong></p>
<ul>
<li>Enable/Disable activity</li>
<li>Logging to Application Event Log</li>
<li>Workflow type:
<ul>
<li>Action: activity completes irrespective of powershell return value,</li>
<li>AuthZ: activity fails if powershell returns an error.</li>
</ul>
</li>
</ul>
<p><strong>Powershell Command</strong></p>
<ul>
<li>Name of powershell cmdlet or full path to script</li>
<li>Specify plugin (in case using another library, eg., <a href="http://www.messageops.com/software/messageops-microsoft-online-powershell-extensions">MessageOps</a>)</li>
<li>Parameters passed via WorkflowData parameter generated earlier (see above).</li>
</ul>
<p><strong>Return Values</strong></p>
<ul>
<li>Specify WorkflowData parameters to return the result of the powershell command (&#8220;Succeeded&#8221;,&#8221;Failed&#8221;) and/or a detailed message. These can then be used in a subsequent activity, such as a Notification.</li>
</ul>
<p><strong>Post-Execution Behaviour</strong></p>
<ul>
<li>Reverse simple changes in certain cases eg.,
<ul>
<li>Clear a change password attribute on both success and failure,</li>
<li>Clear a check box if the requested change failed.</li>
</ul>
</li>
</ul>
<p><strong>Connection Settings</strong></p>
<ul>
<li>If using Remote Powershell specify the remote server name and connection details. Assumes &#8220;localhost&#8221; if left blank.</li>
<li>Specify BPOS username and password of an administrator account.</li>
<li>Note that <strong>passwords must be re-entered</strong> whenever the activity is edited.</li>
</ul>
<h3>The Code</h3>
<p>You can download a copy of the activity <a href="http://www.wapshere.com/dl/BPOS%20Powershell.zip">here</a>.</p>
<p>Please note you will need to fix the references and compile it yourself.</p>
<blockquote><p>This code is provided as an <em>example only</em> with <strong>no warranty or support</strong>. It may not be applicable for your environment. Make sure you understand it and adapt it appropriately.</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/bpos-powershell-activity/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Updating WorkflowData from a custom workflow</title>
		<link>http://www.wapshere.com/missmiis/updating-workflowdata-from-a-custom-workflow</link>
		<comments>http://www.wapshere.com/missmiis/updating-workflowdata-from-a-custom-workflow#comments</comments>
		<pubDate>Tue, 08 Mar 2011 20:26:18 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1311</guid>
		<description><![CDATA[Continuing in my recent theme of communicating information back from a custom workflow (see also Updating RequestStatusDetail and Passing Data via the Request Object), this post shows how you can write information into WorkflowData parameters to then use later in the workflow, perhaps putting it somewhere with the Function Evaluator, or using it in an Email [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/03/wf-return-values.jpg"></a>Continuing in my recent theme of communicating information back from a custom workflow (see also <a href="http://www.wapshere.com/missmiis/updating-requeststatusdetail">Updating RequestStatusDetail</a> and <a href="http://www.wapshere.com/missmiis/passing-data-from-a-custom-workflow-via-the-request-object">Passing Data via the Request Object</a>), this post shows how you can write information into WorkflowData parameters to then use later in the workflow, perhaps putting it somewhere with the Function Evaluator, or using it in an Email Template.</p>
<p><span id="more-1311"></span></p>
<h3>The Basic Code</h3>
<p>The code, which you will put into your custom workflow, is very simple. To start with, get the containing workflow which gives you access to any existing WorkflowData parameters:</p>
<pre>'' Get containing Workflow
Dim containingWorkflow As SequentialWorkflow = Nothing
If Not SequentialWorkflow.TryGetContainingWorkflow(Me, containingWorkflow) Then
    Throw New InvalidOperationException("Unable to get Containing Workflow")
End If</pre>
<p>Then add a new parameter with the data you want to communicate back out of your code:</p>
<pre>    containingWorkflow.WorkflowDictionary.Add("MyParam", "My Value")</pre>
<p>You can then use [//WorkflowData/MyParam]  in subsequent steps within the same FIM workflow.</p>
<h3>Pass the Parameter to the Custom Workflow</h3>
<p>You can make your workflow a bit more flexible if you allow the user to set the parameter name when configuring the activity.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/03/wf-return-values.jpg"><img class="alignnone size-full wp-image-1314" title="wf return values" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/03/wf-return-values.jpg" alt="" width="397" height="30" /></a></p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/03/wf-return-values.jpg"></a></p>
<p>Here I&#8217;ve added a string parameter to my workflow UI. This allows the user to enter a WorkflowData parameter while configuring the activity. The following code snippet will return a message in the parameter.</p>
<p><code>'' Write the return details if it was requested<br />
If Me.ResultDetails.Contains("WorkflowData") Then<br />
    Dim seperator() As Char = {"[", "]", "\", "/"}<br />
    Dim wfkey As String = Me.ResultDetails.Split(seperator, StringSplitOptions.RemoveEmptyEntries)(1)<br />
    containingWorkflow.WorkflowDictionary.Add(wfkey, returnMessage)<br />
End If</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/updating-workflowdata-from-a-custom-workflow/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Updating RequestStatusDetail</title>
		<link>http://www.wapshere.com/missmiis/updating-requeststatusdetail</link>
		<comments>http://www.wapshere.com/missmiis/updating-requeststatusdetail#comments</comments>
		<pubDate>Sat, 19 Feb 2011 08:00:17 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[Logs]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1301</guid>
		<description><![CDATA[Not long ago I wrote a post about writing status messages back to the Request object from a custom workflow. I used custom attributes because I was following the only documentation I could find. But Henrik said &#8220;why don&#8217;t you use RequestStatusDetail?&#8221; &#8211; and actually the reasons were a. I hadn&#8217;t thought of it, and [...]]]></description>
			<content:encoded><![CDATA[<p>Not long ago I wrote a post about <a href="http://www.wapshere.com/missmiis/passing-data-from-a-custom-workflow-via-the-request-object">writing status messages back to the Request</a> object from a custom workflow. I used custom attributes because I was following <a href="http://msdn.microsoft.com/en-us/library/ff463694.aspx">the only documentation I could find</a>. But <a href="http://idmcrisis.com/">Henrik</a> said &#8220;why don&#8217;t you use RequestStatusDetail?&#8221; &#8211; and actually the reasons were a. I hadn&#8217;t thought of it, and b. I didn&#8217;t know how.</p>
<p>But now I&#8217;ve had a play with it, and figured out how to update the field (it needs to be in XML) and why it&#8217;s very cool to do so (it makes the message easily available to the user).<br />
<span id="more-1301"></span><br />
In my custom workflow I have the following activities:</p>
<ul>
<li>A CurrentRequestActivity</li>
<li>A code activity, and</li>
<li>A ResourceUpdateActivity.</li>
</ul>
<p>During my code activity I have set two variables:</p>
<ul>
<li>Boolean &#8220;success&#8221;, and</li>
<li>String &#8220;returnMessage&#8221;.</li>
</ul>
<p>Now at the end of my code activity I add the following code to prepare the ground for the ResourceUpdateActivity (which I&#8217;ve named &#8220;updateRequestDetails&#8221;) that writes the message back.</p>
<pre>        Me.updateRequestDetails_ActorId1 = New Guid(FIMADMIN_GUID)
        Me.updateRequestDetails_ResourceId1 = Me.currentRequestActivity.CurrentRequest.ObjectID
        Dim returnXML As String
        Dim returnLevel As String
        If success Then
            returnLevel = "Information"
        Else
            returnLevel = "Error"
        End If
        returnXML = "&lt;RequestStatusDetail xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" " _
                    &amp; "xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" DetailLevel=""" &amp; returnLevel &amp; """ " _
                    &amp; "EntryTime=""" &amp; DateTime.Now.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.000'") &amp; """&gt;" _
                    &amp; returnMessage &amp; "&lt;/RequestStatusDetail&gt;"
        Dim updateInstruction As New UpdateRequestParameter
        updateInstruction.PropertyName = "RequestStatusDetail"
        updateInstruction.Mode = UpdateMode.Insert
        updateInstruction.Value = returnXML
        Me.updateRequestDetails_UpdateParameters1 = New UpdateRequestParameter() {updateInstruction}</pre>
<p>I&#8217;ve used the FIM Admin account to write the message, and I had to explicitly give it permission to write to the RequestStatusDetail attribute of the Request resource type. You could also use the requestor&#8217;s account to update the field (Me.updateRequestDetails_ActorId1 = Me.currentRequestActivity.CurrentRequest.Creator), but you will still need to explicitly grant permissions with an MPR (have a look at the &#8220;Request Management: Request creators can&#8230;&#8221; MPRs to see what you need to do).</p>
<p>And here&#8217;s a picture of the Request after the custom workflow activity failed.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/02/requeststatusdetail.jpg"><img class="alignnone size-full wp-image-1305" title="requeststatusdetail" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/02/requeststatusdetail.jpg" alt="" width="544" height="361" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/updating-requeststatusdetail/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Passing data from a custom Workflow via the Request object</title>
		<link>http://www.wapshere.com/missmiis/passing-data-from-a-custom-workflow-via-the-request-object</link>
		<comments>http://www.wapshere.com/missmiis/passing-data-from-a-custom-workflow-via-the-request-object#comments</comments>
		<pubDate>Thu, 06 Jan 2011 20:33:04 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[Email Template]]></category>
		<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1223</guid>
		<description><![CDATA[I have been writing an activity that resets forgotten BPOS passwords (more on that later) and I wanted to include a Notification activity that would inform the user about the password reset request. To make the notification useful I want to include whether the reset succeeded, and if it failed, what the reason was. To [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-02-MPR-allowing-request-creators-to-read-new-attribs.jpg"></a>I have been writing an activity that resets forgotten BPOS passwords (more on that later) and I wanted to include a Notification activity that would inform the user about the password reset request. To make the notification useful I want to include whether the reset succeeded, and if it failed, what the reason was. To do this I need to be able to pass information from the custom &#8220;Change BPOS Password&#8221; activity to the Notification activity.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-07-workflow-notif-detail1.jpg"><img class="alignnone size-full wp-image-1226" title="pass 07 workflow - notif detail" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-07-workflow-notif-detail1.jpg" alt="" width="736" height="323" /></a></p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-07-workflow-notif-detail.jpg"></a><br />
<span id="more-1223"></span></p>
<h3>What does the documentation say?</h3>
<p>The first thing I did was consult the official document <a href="http://msdn.microsoft.com/en-us/library/ff463694.aspx">Developing Custom Activities and Workflows</a>. The only thing I could find about passing data was this:</p>
<blockquote>
<h3>Passing Data between Workflow Phases</h3>
<div>
<p>Some scenarios may require that an authorization workflow share some information with an action workflow that runs for the same request. (For more information, see <a href="http://msdn.microsoft.com/en-us/library/ee652475.aspx">Request Processing</a>.) The recommended approach for accomplishing this scenario is as follows:</p>
<h4>Passing Data from an Authorization Workflow Activity to an Action Workflow</h4>
<ol>
<li>Create and bind one or more attributes to the <a href="http://msdn.microsoft.com/en-us/library/ee652273.aspx">Request</a> resource type in the FIM schema that can store the information that you want to share between activities. You can do this through Web services or by using the FIM Portal.</li>
<li>Grant permissions to the person or resource that will be assigned to the <strong>ActorID</strong> of the activity to read and modify the new attribute. Note that this step is not required if the <strong>ActorID</strong> is set to the FIM Service Account resource. For more information, see <a href="http://msdn.microsoft.com/en-us/library/microsoft.resourcemanagement.workflow.activities.aspx">Microsoft.ResourceManagement.Workflow.Activities</a>.</li>
<li>In the authorization workflow activity, use the <strong>UpdateResourceActivity</strong> activity to set the desired value on the new attribute.</li>
<li>In the action workflow activity, use the <strong>ReadResourceActivity</strong> to get the value of the new attribute.</li>
</ol>
</div>
</blockquote>
<p>However this didn&#8217;t seem to address my simpler situation. I&#8217;m not trying to pass a value between an AuthZ and an Action workflow &#8211; I just want to pass the data between two steps in the same Action workflow. Perhaps I can use a WorkflowData parameter?</p>
<p>But as I thought about it I decided that maybe the method mentioned in the documentation was the right way to go. Essentially I am updating the request object itself with the results of the activity, and this then forms a permanent record. To jump ahead in the story, here&#8217;s what a request object looks like after a failed password reset operation. I think that&#8217;s pretty useful!</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-11-failed-request.jpg"><img class="alignnone size-full wp-image-1228" title="pass 11 failed request" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-11-failed-request.jpg" alt="" width="618" height="233" /></a></p>
<h3>Create the Attributes</h3>
<p>So following step one from the documentation, I created the following attributes and bound them to the Request resource type:</p>
<ul>
<li>&#8220;Activity Status&#8221; &#8211; indexed string. This will normally contain &#8220;Succeeded&#8221; or &#8220;Failed&#8221;.</li>
<li>&#8220;Activity Status Message&#8221; &#8211; unindexed string. Here I can put extra info about why an activity failed.</li>
</ul>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-011.jpg"><img class="alignnone size-full wp-image-1233" title="pass 01" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-011.jpg" alt="" width="632" height="161" /></a></p>
<h3>Grant permissions to the new attributes</h3>
<p>I added the new attributes to both of the following MPRs:  <strong>Request management: Request creators can read their approval resources</strong> and <strong>Request management: Request participants can read their request resources</strong>.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-02-MPR-allowing-request-creators-to-read-new-attribs.jpg"><img class="alignnone size-full wp-image-1235" title="pass 02 MPR allowing request creators to read new attribs" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-02-MPR-allowing-request-creators-to-read-new-attribs.jpg" alt="" width="634" height="306" /></a></p>
<p>I also updated <strong>Administration: Administrators can control requests </strong>because I&#8217;m using the FIM service account to make the changes to the request (note the documentation above says you don&#8217;t have to do that if using the service account . I think that may be a mistake as rights always have to be granted, and I certainly had to).</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-04-MPR-allowing-admin-to-update-new-attribs1.jpg"><img class="alignnone size-full wp-image-1236" title="pass 04 MPR allowing admin to update new attribs" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-04-MPR-allowing-admin-to-update-new-attribs1.jpg" alt="" width="644" height="388" /></a></p>
<h3>Code the custom workflow activity to update the Request</h3>
<p>&nbsp;</p>
<table border=0>
<tbody>
<tr>
<td width=156><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-vb-workflow1.jpg"><img class="alignnone size-full wp-image-1238" title="pass vb workflow" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-vb-workflow1.jpg" alt="" width="146" height="274" /></a></td>
<td>The custom workflow must include a CurrentRequestActivity to get the details of the current request &#8211; I think this is probably standard anyway.</p>
<p>Here I&#8217;m finishing up with two UpdateResourceActivities which update respectively the &#8220;Activity Status&#8221; and &#8220;Activity Status Message&#8221;.</p>
<p>And I also need a code activity somewhere to feed the values to the UpdateResourceActivities. I tack this at the end of the code activity where I&#8217;m running my password reset code, because that&#8217;s where I have easy access to the results of the reset attempt. This code that sets up the UpdateResourceActivities is below. Note I am using the Built-In Admin account to update the request object. The variable &#8220;failureMessage&#8221; was already set earlier in the code.
</td>
</tr>
</tbody>
</table>
<p><code>
<pre>
    Const FIMADMIN_GUID As String = "7fb2b853-24f0-4498-9534-4e10589723c4"

    '' Prepare variables for UpdateRequest activities which write the activity status back to the Request object.
    Me.updateRequest1_ActorId1 = New Guid(FIMADMIN_GUID)
    Me.updateRequest1_ResourceId1 = Me.currentRequestActivity1.CurrentRequest.ObjectID
    Dim updateInstruction1 As New UpdateRequestParameter
    updateInstruction1.PropertyName = "ActivityStatus"
    If success Then
        updateInstruction1.Mode = UpdateMode.Modify
        updateInstruction1.Value = "Succeeded"
    Else
        updateInstruction1.Mode = UpdateMode.Modify
        updateInstruction1.Value = "Failed"
    End If

    Me.updateRequest1_UpdateParameters1 = New UpdateRequestParameter() {updateInstruction1}
    Me.updateRequest2_ActorId1 = New Guid(FIMADMIN_GUID)
    Me.updateRequest2_ResourceId1 = Me.currentRequestActivity1.CurrentRequest.ObjectID
    Dim updateInstruction2 As New UpdateRequestParameter
    updateInstruction2.PropertyName = "ActivityStatusMessage"
    If success Then
        'No message
    Else
        updateInstruction2.Mode = UpdateMode.Modify
        updateInstruction2.Value = failureMessage
    End If
    Me.updateRequest2_UpdateParameters1 = New UpdateRequestParameter() {updateInstruction2}
</pre>
<p></code></p>
<h3>Accessing the values from an Email Template</h3>
<p>My final step is to create the Email Template that will be used by the Notification Activity.</p>
<p>For the Subject:<br />
<code>
<pre>BPOS Password Reset [//Request/ActivityStatus]</pre>
<p></code></p>
<p>And in the body I add the following:<br />
<code>
<pre>&lt;p class="MsoNormal"&gt;&lt;b&gt;Request Status:&lt;/b&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;[//Request/ActivityStatus]&lt;/p&gt;
&lt;p class="MsoNormal"&gt;[//Request/ActivityStatusMessage]&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;o:p&gt;&nbsp;&lt;/o:p&gt;&lt;/p&gt;</pre>
<p></code></p>
<p>The result is that the password reset operation runs and depending on the result of the attempt, the user gets one of these emails:</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-12-email-1.jpg"><img src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-12-email-1.jpg" alt="" title="pass 12 email 1" width="460" height="295" class="alignnone size-full wp-image-1244" /></a></p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-12-email-2.jpg"><img src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/pass-12-email-2.jpg" alt="" title="pass 12 email 2" width="527" height="350" class="alignnone size-full wp-image-1245" /></a></p>
<p>And I think that that&#8217;s pretty cool!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/passing-data-from-a-custom-workflow-via-the-request-object/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Adding a Radio Control to a FIM Custom Workflow UI</title>
		<link>http://www.wapshere.com/missmiis/adding-a-radio-control-to-a-fim-custom-workflow-ui</link>
		<comments>http://www.wapshere.com/missmiis/adding-a-radio-control-to-a-fim-custom-workflow-ui#comments</comments>
		<pubDate>Tue, 04 Jan 2011 19:10:27 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1218</guid>
		<description><![CDATA[Following on from the last post about adding a CheckBox, here&#8217;s how I added a RadioButtonList to a custom workflow UI.
Note: I have also uploaded a version of this to the Technet Wiki: How to Add a RadioButtonList to a FIM Custom Workflow UI


Here I&#8217;m adding a control which allows me to set a Logging [...]]]></description>
			<content:encoded><![CDATA[<p>Following on from the <a href="http://www.wapshere.com/missmiis/adding-a-checkbox-to-a-fim-custom-workflow-ui">last post</a> about adding a CheckBox, here&#8217;s how I added a RadioButtonList to a custom workflow UI.</p>
<blockquote><p>Note: I have also uploaded a version of this to the Technet Wiki: <a href="http://social.technet.microsoft.com/wiki/contents/articles/how-to-add-a-radiobuttonlist-to-a-fim-custom-workflow-ui.aspx">How to Add a RadioButtonList to a FIM Custom Workflow UI<br />
</a></p></blockquote>
<p><span id="more-1218"></span><br />
Here I&#8217;m adding a control which allows me to set a Logging Level. I then use the value Me.LoggingLevel in my code to decide which messages to write to the event log.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/radio-01.jpg"><img class="alignnone size-medium wp-image-1219" title="radio 01" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/radio-01-300x26.jpg" alt="" width="300" height="26" /></a></p>
<h3>Define the property in the Activity code</h3>
<p>The radio control will return a string value so I need to define the property in the Activity code.</p>
<div></div>
<p><code></p>
<pre>    '''
    '''  Identifies the target attribute
    '''
    Public Shared LoggingLevelProperty As DependencyProperty = DependencyProperty.Register("LoggingLevel", GetType(System.String), GetType(MyActivity))
    &lt;Description("Please specify the target attribute")&gt; _
    &lt;DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)&gt; _
    &lt;Browsable(True)&gt; _
    Public Property LoggingLevel() As String
        Get
            Return DirectCast(MyBase.GetValue(MyActivity.LoggingLevelProperty), [String])
        End Get
        Set(ByVal value As String)
            MyBase.SetValue(MyActivity.LoggingLevelProperty, value)
        End Set
    End Property</pre>
<p> </p>
<p></code></p>
<h3>UI code functions</h3>
<p>Add the following functions to your UI code:</p>
<div></div>
<p><code></p>
<pre>#Region "Radio Functions"
    Private Function AddTableRowRadioList(ByVal labelText As [String], ByVal controlID As [String], _
                                          ByVal radioOptions As String(), _
                                          ByVal defaultValue As [String]) As TableRow
        Dim row As New TableRow()
        Dim labelCell As New TableCell()
        Dim controlCell As New TableCell()
        Dim label As New Label()
        Dim radioList As New RadioButtonList()
        Dim item As String

        label.Text = labelText
        label.CssClass = MyBase.LabelCssClass
        labelCell.Controls.Add(label)

        radioList.ID = controlID
        For Each item In radioOptions
            radioList.Items.Add(New ListItem(item, item))
        Next
        radioList.SelectedValue = defaultValue
        radioList.RepeatDirection = RepeatDirection.Horizontal
        controlCell.Controls.Add(radioList)
        row.Cells.Add(labelCell)
        row.Cells.Add(controlCell)
        Return row
    End Function

    Private Function GetRadioSelection(ByVal radioListID As String) As String
        Dim radioList As RadioButtonList = DirectCast(Me.FindControl(radioListID), RadioButtonList)
        Return radioList.SelectedValue
    End Function

    Private Sub SetRadioSelection(ByVal radioListID As String, ByVal radioSelection As String)
        Dim radioList As RadioButtonList = DirectCast(Me.FindControl(radioListID), RadioButtonList)
        If radioList IsNot Nothing Then
            radioList.SelectedValue = radioSelection
        Else
            radioList.SelectedValue = radioList.Items.Item(0).Text
        End If
    End Sub

    Private Sub SetRadioListReadOnlyOption(ByVal radioListID As String, ByVal [readOnly] As Boolean)
        Dim radioList As RadioButtonList = DirectCast(Me.FindControl(radioListID), RadioButtonList)
        radioList.Enabled = Not [readOnly]
    End Sub

    Protected Overrides Sub CreateChildControls()
        Dim controlLayoutTable As Table
        controlLayoutTable = New Table()

        'Width is set to 100% of the control size
        controlLayoutTable.Width = Unit.Percentage(100.0)
        controlLayoutTable.BorderWidth = 0
        controlLayoutTable.CellPadding = 2
        ' The following lines add the Radio Control
        Dim loggingOptions As String() = {"Normal", "Verbose"}
        controlLayoutTable.Rows.Add(Me.AddTableRowRadioList("Logging Level:", "txtLoggingLevel", loggingOptions, "Normal"))
        Me.Controls.Add(controlLayoutTable)

        MyBase.CreateChildControls()
    End Sub

#End Region</pre>
<p> </p>
<p></code></p>
<h3>Modify the default methods of the UI code</h3>
<p>Finally modify the default metods of the UI code to handle the value.</p>
<div></div>
<p><code></p>
<pre>    Public Overrides Function GenerateActivityOnWorkflow(ByVal workflow As SequentialWorkflow) As Activity
        If Not Me.ValidateInputs() Then
            Return Nothing
        End If
        Dim MyActivity As New MyActivity()
        MyActivity.LoggingLevel = Me.GetRadioSelection("txtLoggingLevel")
        Return MyActivity
    End Function

    Public Overrides Sub LoadActivitySettings(ByVal activity As Activity)
        Dim MyActivity As MyActivity = TryCast(activity, MyActivity)
        If MyActivity IsNot Nothing Then
            Me.SetRadioSelection("txtLoggingLevel", MyActivity.LoggingLevel)
        End If
    End Sub

    Public Overrides Function PersistSettings() As ActivitySettingsPartData
        Dim data As New ActivitySettingsPartData()
        data("LoggingLevel") = Me.GetRadioSelection("txtLoggingLevel")
        Return data
    End Function

    Public Overrides Sub RestoreSettings(ByVal data As ActivitySettingsPartData)
        If data IsNot Nothing Then
            Me.SetRadioSelection("txtLoggingLevel", DirectCast(data("LoggingLevel"), String))
        End If
    End Sub

    Public Overrides Sub SwitchMode(ByVal mode As ActivitySettingsPartMode)
        Dim [readOnly] As Boolean = (mode = ActivitySettingsPartMode.View)
        Me.SetRadioListReadOnlyOption("txtLoggingLevel", [readOnly])
    End Sub</pre>
<p> </p>
<p></code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/adding-a-radio-control-to-a-fim-custom-workflow-ui/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Adding a CheckBox to a FIM Custom Workflow UI</title>
		<link>http://www.wapshere.com/missmiis/adding-a-checkbox-to-a-fim-custom-workflow-ui</link>
		<comments>http://www.wapshere.com/missmiis/adding-a-checkbox-to-a-fim-custom-workflow-ui#comments</comments>
		<pubDate>Tue, 04 Jan 2011 18:30:46 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1213</guid>
		<description><![CDATA[The only examples of custom workflows I&#8217;ve seen so far use text boxes for all data entry on the UI. Here&#8217;s how I went about adding a check box to my UI.
Note: I have also posted a version of this to the Technet Wiki: How to Add a CheckBox to a FIM Custom Workflow UI


The [...]]]></description>
			<content:encoded><![CDATA[<p>The only examples of custom workflows I&#8217;ve seen so far use text boxes for all data entry on the UI. Here&#8217;s how I went about adding a check box to my UI.</p>
<blockquote><p>Note: I have also posted a version of this to the Technet Wiki: <a href="http://social.technet.microsoft.com/wiki/contents/articles/how-to-add-a-checkbox-to-an-fim-custom-workflow-ui.aspx">How to Add a CheckBox to a FIM Custom Workflow UI<br />
</a></p></blockquote>
<p><span id="more-1213"></span><br />
The code I&#8217;m sharing here adds an &#8220;Enabled&#8221; checkbox to the workflow UI in the FIM Portal. Whether or not it is ticked returns a boolean value which you can then reference in your code.</p>
<p><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/checkbox-01.jpg"><img class="alignnone size-full wp-image-1214" title="checkbox 01" src="http://www.wapshere.com/missmiis/wp-content/uploads/2011/01/checkbox-01.jpg" alt="" width="275" height="29" /></a></p>
<h3>Define the boolean property in the Activity code</h3>
<p>This is pretty much like the regular way UI properties are defined, just typing it as a Boolean instead of a String.</p>
<div></div>
<p><code></p>
<pre>    '''
    '''  Identifies the target attribute
    '''
    Public Shared WFEnabledProperty As DependencyProperty = DependencyProperty.Register("WFEnabled", GetType(System.Boolean), GetType(MyActivity))
    &lt;Description("Please specify the target attribute")&gt; _
    &lt;DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)&gt; _
    &lt;Browsable(True)&gt; _
    Public Property WFEnabled() As Boolean
        Get
            Return DirectCast(MyBase.GetValue(MyActivity.WFEnabledProperty), [Boolean])
        End Get
        Set(ByVal value As Boolean)
            MyBase.SetValue(MyActivity.WFEnabledProperty, value)
        End Set
    End Property</pre>
<p> </p>
<p></code></p>
<h3>UI code functions</h3>
<p>Add the following functions to your UI code:</p>
<div></div>
<p><code></p>
<pre>#Region "CheckBox Functions"
    Private Function AddTableRowCheckBox(ByVal labelText As [String], ByVal controlID As [String], _
                                         ByVal defaultValue As [Boolean]) As TableRow
        Dim row As New TableRow()
        Dim labelCell As New TableCell()
        Dim controlCell As New TableCell()
        Dim label As New Label()
        Dim checkbox As New CheckBox()

        label.Text = labelText
        label.CssClass = MyBase.LabelCssClass
        labelCell.Controls.Add(label)

        checkbox.ID = controlID
        checkbox.Checked = defaultValue

        controlCell.Controls.Add(checkbox)
        row.Cells.Add(labelCell)
        row.Cells.Add(controlCell)
        Return row
    End Function

    Private Function GetIsChecked(ByVal checkBoxID As String) As Boolean
        Dim checkBox As CheckBox = DirectCast(Me.FindControl(checkBoxID), CheckBox)
        Return checkBox.Checked
    End Function

    Private Sub SetIsChecked(ByVal checkBoxID As String, ByVal isChecked As Boolean)
        Dim checkBox As CheckBox = DirectCast(Me.FindControl(checkBoxID), CheckBox)
        If checkBox IsNot Nothing Then
            checkBox.Checked = isChecked
        Else
            checkBox.Checked = True
        End If
    End Sub

    Private Sub SetCheckBoxReadOnlyOption(ByVal textBoxID As String, ByVal [readOnly] As Boolean)
        Dim checkBox As CheckBox = DirectCast(Me.FindControl(textBoxID), CheckBox)
        checkBox.Enabled = Not [readOnly]
    End Sub

#End Region</pre>
<p> </p>
<p></code></p>
<h3>Modify the default methods of the UI code</h3>
<p>Finally modify the default metods of the UI code to handle the value.</p>
<div></div>
<p><code></p>
<pre>    Public Overrides Function GenerateActivityOnWorkflow(ByVal workflow As SequentialWorkflow) As Activity
        If Not Me.ValidateInputs() Then
            Return Nothing
        End If
        Dim MyActivity As New MyActivity()
        MyActivity.WFEnabled = Me.GetIsChecked("bWFEnabled")
        Return MyActivity
    End Function

    Public Overrides Sub LoadActivitySettings(ByVal activity As Activity)
        Dim MyActivity As MyActivity = TryCast(activity, MyActivity)
        If MyActivity IsNot Nothing Then
            Me.SetIsChecked("bWFEnabled", MyActivity.WFEnabled)
        End If
    End Sub

    Public Overrides Function PersistSettings() As ActivitySettingsPartData
        Dim data As New ActivitySettingsPartData()
        data("WFEnabled") = Me.GetIsChecked("bWFEnabled")
        Return data
    End Function

    Public Overrides Sub RestoreSettings(ByVal data As ActivitySettingsPartData)
        If data IsNot Nothing Then
            Me.SetIsChecked("bWFEnabled", DirectCast(data("WFEnabled"), Boolean))
        End If
    End Sub

    Public Overrides Sub SwitchMode(ByVal mode As ActivitySettingsPartMode)
        Dim [readOnly] As Boolean = (mode = ActivitySettingsPartMode.View)
        Me.SetCheckBoxReadOnlyOption("bWFEnabled", [readOnly])
    End Sub

    Protected Overrides Sub CreateChildControls()
        Dim controlLayoutTable As Table
        controlLayoutTable = New Table()

        'Width is set to 100% of the control size
        controlLayoutTable.Width = Unit.Percentage(100.0)
        controlLayoutTable.BorderWidth = 0
        controlLayoutTable.CellPadding = 2
        ' The following line adds the CheckBox
        controlLayoutTable.Rows.Add(Me.AddTableRowCheckBox("Enabled:", "bMyCheckBox", True))
        Me.Controls.Add(controlLayoutTable)

        MyBase.CreateChildControls()
    End Sub</pre>
<p> </p>
<p></code></p>
<h3>Using the value</h3>
<p>Once all that&#8217;s done I simply access the value in my Activity code using Me.WFEnabled.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/adding-a-checkbox-to-a-fim-custom-workflow-ui/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Password Sync from AD to BPOS</title>
		<link>http://www.wapshere.com/missmiis/password-sync-from-ad-to-bpos</link>
		<comments>http://www.wapshere.com/missmiis/password-sync-from-ad-to-bpos#comments</comments>
		<pubDate>Mon, 20 Dec 2010 09:21:09 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[BPOS]]></category>
		<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[Password Sync]]></category>
		<category><![CDATA[VB.NET]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=1135</guid>
		<description><![CDATA[The FIM Sync Service allows passwords to be synchronised from a source AD account to the user&#8217;s accounts in other systems. The sync is done at the point of password change and relies on the Password Change Notification Service, which you must install on your domain controllers.
Many target systems are supported OOB, but for BPOS you have [...]]]></description>
			<content:encoded><![CDATA[<p>The FIM Sync Service allows passwords to be synchronised from a source AD account to the user&#8217;s accounts in other systems. The sync is done at the point of password change and relies on the Password Change Notification Service, which you must install on your domain controllers.</p>
<p>Many target systems are supported OOB, but for BPOS you have to get a little creative and write your own <a href="http://msdn.microsoft.com/en-us/library/ms695388(VS.85).aspx">Password Extension</a>. This post shows you how I did that.</p>
<p>Before I continue however, I do feel obliged to make a comment about the security implications of doing this. There is an argument that passwords used internally should never be sent outside the firewall, and this should be considered in your environment. In my case the source AD was created just for DirSync so the accounts there are not actually being used for anything else, and the password remains different to the user&#8217;s normal desktop logon. Password Sync provides an alternative to the BPOS admin console, and opens the way for reset through the FIM Portal.<br />
<span id="more-1135"></span></p>
<h3>First, you need a connector space which represents your BPOS user objects</h3>
<p>This should be pretty obvious &#8211; for password sync to work it needs a direct join, through the Sync Service, from the source AD to the target account. So you need a connector space with objects that represent your BPOS users and contain, at an absolute minimum, the BPOS Identity which you will use in the powershell password change process.</p>
<p>For ideas about creating a BPOS MA see <a href="http://www.wapshere.com/missmiis/three-different-ways-to-create-a-bpos-management-agent">Three Different Ways to Create a BPOS Management Agent</a>.</p>
<h3>The MA has to run in process</h3>
<p>There is apparently a bug with password extensions in FIM Sync &#8211; if you run the MA in a seperate process the sync service can&#8217;t find the extension and you see these errors in the event log:</p>
<p><code>An unexpected error has occurred during a password set operation.<br />
BAIL: MMS(4948): ma.cpp(373): 0x80040154 (Class not registered)</code></p>
<p>Running the MA in process fixes this problem.</p>
<h3>The Powershell Runspace doesn&#8217;t dispose quickly enough</h3>
<p>I ran into a problem with a System.AppDomainUnloadedException. The password sync worked fine, but then five minutes later the entire miiserver.exe process crashed with this exception.</p>
<p>The Sync service loads an extension dll when it is needed, and keeps it open while it&#8217;s being used. Five minutes after the last use of the dll it runs any termination code and unloads the dll. Clearly something was going wrong here.</p>
<p>At this point I&#8217;m going to shout out a big thanks to <a href="http://briandesmond.com/">Brian Desmond</a> and <a href="http://www.identitytrench.com/">Craig Martin</a> who helped me with this problem. While I still don&#8217;t completely understand what&#8217;s going on, I gather it has something to do with the sync service unloading the password extension while the runspace is still being disposed. The solution I found is to add a System.Threading.Thread.Sleep(0) straight after the Dispose instruction, which is an instruction to wait for other threads to finish.</p>
<h3>Install and configure PCNS</h3>
<p>&nbsp;<br />
I&#8217;m not going to go into this. There is perfectly good documentation (see Peter Geelen&#8217;s overview and troubleshooting tips <a href="http://social.technet.microsoft.com/wiki/contents/articles/troubleshooting-pcns.aspx?wa=wsignin1.0">here</a>) and actually the instructions haven&#8217;t changed since MIIS 2003.</p>
<p>Though I will just add, in case anyone&#8217;s interested, yes you can install PCNS on a server core domain controller (though not an RODC for the obvious reason that it can&#8217;t process a password change).</p>
<h3>Configuring the Sync Service</h3>
<table border="1">
<tbody>
<tr>
<td>Start by enabling Password Sync:Tools &#8211;&gt; Options &#8211;&gt; Enable Password Synchronization</td>
<td><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync1.jpg"></a><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync1.jpg"><img class="alignnone size-medium wp-image-1179" title="pwsync1" src="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync1-300x255.jpg" alt="" width="300" height="255" /></a></td>
</tr>
<tr>
<td>Configure the AD MA which is the source for synchronization of changed passwords:</p>
<p>On the &#8220;Configure Directory Properties&#8221; tab, tick to enable the partition as a password synchronization source.</p>
<p>Click &#8220;Targets&#8221; and select your BPOS MA.</td>
<td><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync2.jpg"></a><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync2.jpg"><img class="alignnone size-medium wp-image-1178" title="pwsync2" src="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync2-300x215.jpg" alt="" width="300" height="215" /></a></td>
</tr>
<tr>
<td>Configure the BPOS MA:</p>
<p>On the &#8220;Configure Extensions&#8221; tab, tick to enable password management and select the password extension dll file (code below).</p>
<p>You will also have to set the connection credentials. As I&#8217;m accessing the MSOnline cmdlets via remote powershell I actually need two sets of credentials &#8211; one for the remote server connection and one for BPOS. However the MA config for password extensions only gives you space for one username and password. I have gone with the completely inelegant workaround of including both usernames and both passwords, separated by a semi-colon, like so:</p>
<p><code>&nbsp;&nbsp;&nbsp;mydomainUser;bposUser<br />
&nbsp;&nbsp;&nbsp;mydomainPassword;bposPassword</code></p>
<p>It&#8217;s not pretty and I don&#8217;t much like it, but it&#8217;s working for now.</td>
<td><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync3.jpg"></a><a href="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync3.jpg"><img class="alignnone size-medium wp-image-1177" title="pwsync3" src="http://www.wapshere.com/missmiis/wp-content/uploads/2010/12/pwsync3-300x224.jpg" alt="" width="300" height="224" /></a></td>
</tr>
</tbody>
</table>
<h3>The Code</h3>
<p>And here is the password extension code.</p>
<p><code> </code></p>
<pre>Imports Microsoft.MetadirectoryServices
Imports System.Management.Automation
Imports System.Management.Automation.Host
Imports System.Management.Automation.Runspaces
Imports System.Diagnostics

Public Class BPOSPasswordExtension

    Implements IMAPasswordManagement

    Dim myRunSpace As Runspace
    Dim bposCred As PSCredential

    Public Sub BeginConnectionToServer(ByVal connectTo As String, _
        ByVal user As String, _
        ByVal password As String) _
        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.BeginConnectionToServer

        Dim PSRemoteUser As String = ""
        Dim PSRemotePassword As String = ""
        Dim BPOSUser As String
        Dim BPOSPassword As String

        If user.Contains(";") Then
            PSRemoteUser = user.Split(";")(0)
            BPOSUser = user.Split(";")(1)
        Else
            BPOSUser = user
        End If

        If password.Contains(";") Then
            PSRemotePassword = password.Split(";")(0)
            BPOSPassword = password.Split(";")(1)
        Else
            BPOSPassword = password
        End If

        If Not connectTo = "" Then
            If PSRemoteUser = "" Or PSRemotePassword = "" Then
                Throw New BadServerCredentialsException("If Connect To is configured in the Password Settings then " _
                &amp; "there must be two semicolon separated Users (psremoteuser;bposuser) and two semicolon separated " _
                &amp; "passwords (psremotepassword;bpospassword)")
                Exit Sub
            End If
        End If

        ' Open powershell runspace
        If connectTo = "" Then
            myRunSpace = OpenLocalRunspace()
        Else
            myRunSpace = OpenRemoteRunspace(connectTo, PSRemoteUser, PSRemotePassword)
        End If
        If myRunSpace Is Nothing Then
            Throw New PasswordExtensionException("Failed to open powershell runspace to server " &amp; connectTo)
            Exit Sub
        End If
        WriteToEventLog("Successfully opened powershell runspace to " &amp; connectTo, EventLogEntryType.Information)

        ' Create credential for attaching to BPOS
        Dim bpospass As New Security.SecureString
        For Each c In BPOSPassword
            bpospass.AppendChar(c)
        Next
        bposCred = New PSCredential(BPOSUser, bpospass)

        ' Add plugin required for BPOS cmdlets
        Dim psh As PowerShell = PowerShell.Create()
        psh.Runspace = myRunSpace
        psh.AddCommand("Add-PSSnapin")
        psh.AddParameter("Name", "Microsoft.Exchange.Transporter")
        Try
            psh.Invoke()
        Catch ex As Exception
            Throw New PasswordExtensionException("Failed to add PS snapin. " &amp; ex.Message)
        End Try

    End Sub

    Public Sub ChangePassword(ByVal csentry As Microsoft.MetadirectoryServices.CSEntry, _
        ByVal OldPassword As String, _
        ByVal NewPassword As String) _
        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.ChangePassword
    End Sub

    Public Sub EndConnectionToServer() _
        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.EndConnectionToServer
        If Not myRunSpace Is Nothing Then
            myRunSpace.Dispose()
            System.Threading.Thread.Sleep(0)
            WriteToEventLog("Runspace closed.", EventLogEntryType.Information)
        End If
    End Sub

    Public Function GetConnectionSecurityLevel() As Microsoft.MetadirectoryServices.ConnectionSecurityLevel _
        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.GetConnectionSecurityLevel
    End Function

    Public Sub RequireChangePasswordOnNextLogin(ByVal csentry As Microsoft.MetadirectoryServices.CSEntry, _
        ByVal fRequireChangePasswordOnNextLogin As Boolean) _
        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.RequireChangePasswordOnNextLogin
        ' This method is not used
        Throw New EntryPointNotImplementedException
    End Sub

    Public Sub SetPassword(ByVal csentry As Microsoft.MetadirectoryServices.CSEntry, _
        ByVal NewPassword As String) _
        Implements Microsoft.MetadirectoryServices.IMAPasswordManagement.SetPassword

        Dim psh As PowerShell = PowerShell.Create()
        Dim psresult As New System.Collections.ObjectModel.Collection(Of PSObject)
        psh.Runspace = myRunSpace

        psh.AddCommand("Set-MSOnlineUserPassword")
        psh.AddParameter("Identity", csentry.DN.ToString)
        psh.AddParameter("Password", NewPassword)
        psh.AddParameter("ChangePasswordOnNextLogon", False)
        psh.AddParameter("Credential", bposCred)
        Try
            psresult = psh.Invoke()
        Catch ex As Exception
            Throw New PasswordExtensionException(ex.Message)
        End Try

        If psh.Streams.Warning.Count &gt; 0 Then
            Throw New PasswordExtensionException(psh.Streams.Warning.Item(0).Message)
        ElseIf psh.Streams.Error.Count &gt; 0 Then
            Throw New PasswordExtensionException(psh.Streams.Error.Item(0).ErrorDetails.Message)
        End If

        psh.Dispose()

    End Sub

#Region "Powershell Functions"
    Private Function PSCredObject(ByVal username As String, ByVal password As String) As PSCredential
        Dim PWSecureString As New Security.SecureString
        Dim c As Char
        For Each c In password
            PWSecureString.AppendChar(c)
        Next
        Dim PSCred As New PSCredential(username, PWSecureString)
        Return PSCred
    End Function

    Private Function OpenRemoteRunspace(ByVal RemoteServer As String, ByVal RemoteUser As String, ByVal RemotePassword As String) As Runspace
        Const SHELL_URI As String = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell"

        ' Open remote powershell session
        Dim serverUri As New Uri("http://" &amp; RemoteServer.ToUpper &amp; ":5985/wsman")
        Dim connectionInfo As New WSManConnectionInfo(serverUri, SHELL_URI, PSCredObject(RemoteUser, RemotePassword))
        Dim myRunSpace As Runspace
        Try
            myRunSpace = RunspaceFactory.CreateRunspace(connectionInfo)
            myRunSpace.Open()
        Catch ex As Exception
            Throw New PasswordExtensionException(ex.Message)
            Return Nothing
            Exit Function
        End Try
        Return myRunSpace
    End Function

    Private Function OpenLocalRunspace() As Runspace
        Dim config As RunspaceConfiguration = RunspaceConfiguration.Create()
        Dim myRunSpace As Runspace
        Try
            myRunSpace = RunspaceFactory.CreateRunspace(config)
            myRunSpace.Open()
        Catch ex As Exception
            Throw New PasswordExtensionException(ex.Message)
            Return Nothing
            Exit Function
        End Try
        Return myRunSpace
    End Function

#End Region

    Public Function WriteToEventLog(ByVal Entry As String, ByVal eventType As EventLogEntryType)
        Dim appName As String = "BPOS Password Sync"
        Dim logName = "Application"

        Dim objEventLog As New EventLog()

        Try
            'Register the App as an Event Source
            'Note: this only works if the service account has rights to the reg key http://support.microsoft.com/kb/842795
            If Not EventLog.SourceExists(appName) Then
                EventLog.CreateEventSource(appName, logName)
            End If

            objEventLog.Source = appName
            objEventLog.WriteEntry(Entry, eventType)
            Return True
        Catch Ex As Exception
            Return False
        End Try
    End Function

End Class</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/password-sync-from-ad-to-bpos/feed</wfw:commentRss>
		<slash:comments>5</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>
	</channel>
</rss>

