<?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; FIM 2010</title>
	<atom:link href="http://www.wapshere.com/missmiis/category/ilm/fim-2010/feed" rel="self" type="application/rss+xml" />
	<link>http://www.wapshere.com/missmiis</link>
	<description>Adventures in identity management</description>
	<lastBuildDate>Sun, 05 Sep 2010 06:41:52 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Generate Unique Attribute Activity</title>
		<link>http://www.wapshere.com/missmiis/generate-unique-attribute-activity</link>
		<comments>http://www.wapshere.com/missmiis/generate-unique-attribute-activity#comments</comments>
		<pubDate>Sun, 05 Sep 2010 06:36:51 +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=966</guid>
		<description><![CDATA[When I first started out with MIIS I found the coding heavy going - but soon realised that the same few extension-writing methods are used for pretty much everything, and the fun bit was just how much I could achieve with them! Now on to FIM and I have struggled every bit as much with learning how to [...]]]></description>
			<content:encoded><![CDATA[<p>When I first started out with MIIS <a href="http://www.wapshere.com/missmiis/what-do-you-mean-i-have-to-write-code">I found the coding heavy going</a> - but soon realised that the same few extension-writing methods are used for pretty much everything, and the fun bit was just how much I could achieve with them!</p>
<p>Now on to FIM and I have struggled every bit as much with learning how to write workflow activities, not particularly helped by the <a href="http://msdn.microsoft.com/en-us/library/ee623726.aspx">official documentation</a> which I can only assume is computer generated, it is so completely unhelpful. But after a <a href="http://www.wapshere.com/missmiis/the-fim-2010-custom-logging-activity-in-vb-net">recent</a> <a href="http://www.wapshere.com/missmiis/how-to-use-the-fim-readresourceactivity-in-vb-net">obsessive</a> <a href="http://www.wapshere.com/missmiis/powershell-activity">spate</a> I&#8217;m finally starting to get the hang of it, and this has brought me to the next fantasy activity on my list &#8211; something to generate a unique attribute, such as an AccountName.<span id="more-966"></span></p>
<h3>The idea</h3>
<p>What I want is a way to generate an attibute based on a series of rules. If I don&#8217;t get a unique value with the first (ideal) rule, then I move to the second, then the third, then if completely desperate the fourth&#8230; Hopefully I&#8217;d have found something good by then.</p>
<p>In an effort to reduce effort (if you can have that) this activity relies on the OOB Function activity to generate the possible strings in order of preference. So here&#8217;s what the workflow looks like configured with three possible choices:</p>
<p><img src="http://www.wapshere.com/images/customwf/gua_fimworkflow.JPG" alt="" /></p>
<p>And here is one of the Function activties, which generates a string and puts it in a WorkflowData parameter:</p>
<p><img src="http://www.wapshere.com/images/customwf/gua_workflowdata.JPG" alt="" /></p>
<h3>Developing the Activity</h3>
<table>
<tbody>
<tr>
<td>This activity is complicated compared to <a href="http://www.wapshere.com/missmiis/powershell-activity">my powershell one</a>, so I&#8217;ll just summarise the steps.</p>
<ol>
<li>A CurrentRequestActivity grabs the workflow details  from FIM,</li>
<li>A ReadTargetActivity gets the details of the object we&#8217;re trying to modify,</li>
<li>A WHILE loop goes through the possible options until a unique value is found (or we run out of options),</li>
<li>Within the WHILE loop, the EnumerateResourcesActivity does the actual looking up of the proposed attribute value, to see if it&#8217;s already taken,</li>
<li>We then have an IfElse statement to handle the lookup results:
<ul>
<li>If we found a good match we write it to the target resource using an UpdateResourceActivity,</li>
<li>If we didn&#8217;t find a match an error is thrown &#8211; this leads to a PostProcessingError in FIM. Of course you could do something else like send an email here.</li>
</ul>
</li>
</ol>
<p>As well there are a bunch of code activities which smooth the way, setting up parameters and passing variables around.</td>
<td><img src="http://www.wapshere.com/images/customwf/gua_workflow.JPG" alt="" /></td>
</tr>
</tbody>
</table>
<h3>The Code</h3>
<p>I&#8217;m not going to post the whole solution this time because I&#8217;ve got all my activities in the one solution and I can&#8217;t be bothered separating it &#8211; but all the code is linked below. There are a couple of other points to add about the workflow construction:</p>
<ul>
<li>For the code activities I just drag them onto the surface, give them a name, and then double-click, which generates the Execute routine.</li>
<li>For all the other activites I use the &#8220;Promote Bindable Properties&#8221; option to generate the code automatically &#8211; I then don&#8217;t touch the generated code at all.</li>
<li>For the While and If conditions I use the &#8220;Declarative Rule Condition&#8221;. (Rules are in comments in the code, but you&#8217;ll have to add them into the WF surface yourself).</li>
</ul>
<p><a href="http://www.wapshere.com/missmiis/generateuniquevalue-vb">GenerateUniqueValue.vb</a></p>
<p><a href="http://www.wapshere.com/missmiis/generateuniquevaluesettingspart-vb">GenerateUniqueValueSettingsPart.vb</a></p>
<h3>PS</h3>
<p>If, like me, you&#8217;re doing this WWF development stuff for the first time, I did find <a href="http://msdn.microsoft.com/en-us/library/dd692925.aspx">this short tutorial</a> very helpful. Despite my rubbishing the MSDN FIM documentation above, there is lots of good stuff there too, of course.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/generate-unique-attribute-activity/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Powershell Activity</title>
		<link>http://www.wapshere.com/missmiis/powershell-activity</link>
		<comments>http://www.wapshere.com/missmiis/powershell-activity#comments</comments>
		<pubDate>Fri, 03 Sep 2010 11:37:56 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[VB.NET]]></category>
		<category><![CDATA[Workflow]]></category>
		<category><![CDATA[powershell]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=905</guid>
		<description><![CDATA[I have been working on a FIM 2010 workflow activity that will run powershell cmdlets and scripts, and I&#8217;m now ready to share the code with you lucky people. The activity should work with both local and remote powershell, bearing in mind the various limitiations that seem to occur when running remote powershell commands through [...]]]></description>
			<content:encoded><![CDATA[<p>I have been working on a FIM 2010 workflow activity that will run powershell cmdlets and scripts, and I&#8217;m now ready to share the code with you lucky people. The activity should work with both local and remote powershell, bearing in mind the various limitiations that seem to occur when running remote powershell commands through code.<span id="more-905"></span></p>
<h3>General Provisos</h3>
<p>I will start by saying that I&#8217;m sharing this as an <em>example </em>and not as supported code. Please do take the time to understand it and modify it as appropriate to your environment. While I do have a production application for this coming up soon I have so far only used it in the lab and may well need to work on it more. This post will remain the place for information about the activity and I will update here if something changes.</p>
<h3>Server Setup</h3>
<p>If you&#8217;re planning on using Remote Powershell then, unless you have WIndows 2008R2 on both the FIM server and the remote server, you will need to make sure the prerequsites are in place. See the <strong>Enabling Remote Powershell</strong> section in <a href="http://www.wapshere.com/missmiis/running-remote-powershell-scripts-from-vb-net">this post</a>.</p>
<h2>About the Activity</h2>
<h3>Lookup Strings</h3>
<p>I&#8217;ve managed to get the activity using lookup strings like [//Target/Email]. At the moment I only support //Target and //Requestor. I haven&#8217;t yet worked out how to do a nice string concatenator with a lookup button like you see on the OOB activities so you have to type the strings yourself.</p>
<p><img src="http://www.wapshere.com/images/customwf/ps_lookupstrings.JPG" alt="" /></p>
<h3>Using the Activity with Local Powershell</h3>
<p>For local powershell it is assumed that the FIM Service account has the rights to run the cmdlet/script. If you need to use a different account then I recommend using a script along with a <a href="http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx">secure password file</a> to inject the credential.</p>
<p><img src="http://www.wapshere.com/images/customwf/ps_local.JPG" alt="" /></p>
<h3>Using the Activity with Remote Powershell</h3>
<p>For remote powershell you also need to fill in the name of the remote server, and the username and password to use when connecting. The remote script will run within the environment of this connection account.</p>
<p><img src="http://www.wapshere.com/images/customwf/ps_remote.JPG" alt="" /></p>
<p>There are a number of points to be aware of when using this activity for remote powershell:</p>
<ul>
<li>The way you enter the server name appears to be relevant. When I enter the FQDN or ip address it works. When I enter a server alias I get &#8220;Access Denied&#8221;.</li>
<li><strong>If you modify the workflow you have to re-enter the password.</strong></li>
<li>Certain commands don&#8217;t work at all through remote powershell, eg., start-transcript and (apparently) the Exchange 2007 cmdlets.</li>
<li>Certain commands don&#8217;t work through remote powershell when run through code, eg., write-host.</li>
<li>The servers at each end must be set up to support remote powershell! If you can&#8217;t do it directly from powershell then don&#8217;t expect this activity to do it.</li>
</ul>
<h3>Error Reporting</h3>
<p>Errors should be reported as a PostProcessingError in the Search Request page of the FIM Portal. In some cases I write extra information to the event log.</p>
<p><img src="http://www.wapshere.com/images/customwf/ps_error.JPG" alt="" /></p>
<h3>Plugins</h3>
<p>I originally had a field to enter a powershell plugin that should be loaded before running the command. I took it out because I figured that would be better done in a script, but the code is still in there commented out if you want to put it back in.</p>
<h3>Writing a suitable powershell script</h3>
<p>You need to take some care with the powershell scripts you write to go with this activity. First, see the comments the Remote Powershell section above for cmdlets I know should be avoided.</p>
<p>The activity will return the <em>last</em> error thrown by the script so it&#8217;s a good idea to make the script stop on the first fatal error it encounters. Setting $ErrorActionPreference = &#8220;stop&#8221; is a start.</p>
<p>Also my code only understands parameters if they have a &#8220;-&#8221; at the front, like with normal powershel cmdlets. This means<em> you must use PARAM</em> when defining script arguments. If you use ARG it won&#8217;t work.</p>
<p>See below for a sample test script.</p>
<h2>Installing</h2>
<p>You are going to have to compile the project into the GAC yourself and then add the AIC in the FIM Portal. See <a href="http://msdn.microsoft.com/en-us/library/ff859524.aspx">here</a> for a walkthrough.</p>
<p>If you don&#8217;t re-sign the project or change any names then the AIC settings are as follows:</p>
<table>
<tbody>
<tr>
<td>Activity Name</td>
<td>FIM.CustomWorkflowActivitiesLibrary.Activities.PowershellActivity</td>
</tr>
<tr>
<td>Assembly Name</td>
<td>FIM.CustomWorkflowActivitiesLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f36c0cc61ccb2b4f</td>
</tr>
<tr>
<td>Is Action Activity</td>
<td>yes</td>
</tr>
<tr>
<td>Type Name</td>
<td>FIM.CustomWorkflowActivitiesLibrary.Activities.WebUIs.PowershellActivitySettingsPart</td>
</tr>
</tbody>
</table>
<h2>Testing</h2>
<h3>Make a Workflow</h3>
<p>Once you&#8217;ve installed the activity, and restarted IIS, you should be able to access it from the Workflow builder. More explanation about how to fill in the form is higher up in this post.</p>
<p><img src="http://www.wapshere.com/images/customwf/ps_wfactivity.JPG" alt="" /></p>
<h3>Create MPRs and Sets to trigger the Workflow</h3>
<p>I made a set based on an attribute, then two MPRs on &#8220;Transition In&#8221; and &#8220;Transition Out&#8221; of the set. Then it was simple matter of toggling the appropriate attribute on a user so it transitioned in and out of the set.</p>
<h3>Test Powershell Script</h3>
<p>Here&#8217;s a test script I&#8217;ve been using. You can see the way I call it in the Remote Powershell picture above. If you run it locally the FIM Service account will need rights to modfy user accounts in AD.</p>
<pre>Param($username, $modifier)

$ErrorActionPreference = "stop"

if ($username -eq "") {throw "Username parameter must be provided."}
if ($modifier -eq "") {throw "Modifier parameter must be provided."}

$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = "LDAP://mydomain.local"
$objSearcher.PageSize = 1000
$objSearcher.Filter = "(&amp;(objectClass=user)(sAMAccountName= $username))"
$objSearcher.SearchScope = "Subtree"

$user = $objSearcher.findone()
if ($user.count -eq 0)
	{throw "No user found with username=" + $username}

$userDN = $user.path

$user = [ADSI]$userDN

$user.Put("extensionAttribute1",$modifier)
$user.SetInfo()</pre>
<h3>And finally, the code</h3>
<p>Here&#8217;s the whole solution. I added this activity on to my existing LoggingLibrary solution so you&#8217;ll find both here.</p>
<p><a href="http://www.wapshere.com/dl/FIM.CustomWorkflowActivitiesLibrary.zip">Download here</a></p>
<h3>A last word</h3>
<p>Like I said, I&#8217;m still learning this stuff, and this activity is not in production so I may find problems. If you do have feedback please comment here.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/powershell-activity/feed</wfw:commentRss>
		<slash:comments>4</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 [...]]]></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>FIM, the openldapXMA and MD5 passwords</title>
		<link>http://www.wapshere.com/missmiis/fim-the-openldapxma-and-md5-passwords</link>
		<comments>http://www.wapshere.com/missmiis/fim-the-openldapxma-and-md5-passwords#comments</comments>
		<pubDate>Mon, 23 Aug 2010 17:03:17 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[OpenLDAP]]></category>
		<category><![CDATA[Password Sync]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=945</guid>
		<description><![CDATA[I had a problem today setting userPassword in openLDAP 3, using the openldapXMA on FIM 2010. I needed to encode the password with MD5 and it looks like there is a change with the .NET libraries running on Windows 2008 x64.  The usual published .NET examples using the System.Security.Cryptography generate a completely different MD5 hash to [...]]]></description>
			<content:encoded><![CDATA[<p>I had a problem today setting userPassword in openLDAP 3, using the <a href="http://www.wapshere.com/missmiis/compiling-the-openldap-xma-to-use-with-fim-2010">openldapXMA on FIM 2010</a>. I needed to encode the password with MD5 and it looks like there is a change with the .NET libraries running on Windows 2008 x64.<span id="more-945"></span> </p>
<p>The usual published .NET examples using the System.Security.Cryptography generate a completely different MD5 hash to that generated by openLDAP &#8211; and <a href="http://www.codeproject.com/Messages/3164743/MD5CryptoServiceProviders-ComputeHash-doesnt-match.aspx">I&#8217;m not the only one to have noticed</a>.</p>
<p>After much hunting around I found <a href="http://forums.asp.net/p/1498290/3851755.aspx">this thread</a> where belcherman was the man with the answer: you have to set the encoding to UTF7. The following function does the trick:</p>
<p><code></p>
<pre>    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</pre>
<p>  </code><br />
Then, when setting the initial userPassword in your provisioning code:<br />
<code>
<pre>
        CSEntry("userPassword").Value = "{MD5}" &#038; GenerateHash("FirstP@ssword")
</pre>
<p></code></p>
<h3>Password Sync</h3>
<p>It is simple enough to modify the openldapXMA Password Extension to set the hashed password.</p>
<p>In OpenLDAPUtils.cs add the C# version of the GenerateHash function I included above. (If you don&#8217;t know how to do this see <a href="http://www.developerfusion.com/tools/convert/vb-to-csharp/">http://www.developerfusion.com/tools/convert/vb-to-csharp</a>)</p>
<p>Then change the SetPassword subroutine in the same file:<br />
<code>
<pre>
        public void SetPassword(string newPassword, CSEntry csentry)
        {
            DirectoryAttributeModification pwdChange = new DirectoryAttributeModification();

            pwdChange.Name = "userPassword";
            // CW: Changed to use an MD5 hash
            // pwdChange.Add(newPassword);
            pwdChange.Add("{MD5}" + GenerateHash(newPassword));
            pwdChange.Operation = DirectoryAttributeOperation.Replace;

            ModifyRequest request = new ModifyRequest(csentry.DN.ToString(), pwdChange);
            try
            {
                DirectoryResponse response = m_Ldap.SendRequest(request);
            }
            catch (Exception e)
            {
                MiisExceptionsManager.ReportErrorToMiis(e.Message);

            }

        }
</pre>
<p></code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/fim-the-openldapxma-and-md5-passwords/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to use the FIM ReadResourceActivity, in VB.NET</title>
		<link>http://www.wapshere.com/missmiis/how-to-use-the-fim-readresourceactivity-in-vb-net</link>
		<comments>http://www.wapshere.com/missmiis/how-to-use-the-fim-readresourceactivity-in-vb-net#comments</comments>
		<pubDate>Sun, 22 Aug 2010 16:23:07 +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=927</guid>
		<description><![CDATA[Sometimes, while trying to figure out FIM 2010 custom workflows, it seems to me that everyone else finds this stuff bleedingly obvious &#8211; but then I remind myself that actually, I&#8217;m not a complete idiot, and if I&#8217;m finding it all a bit like wading through chest-high treacle, with submerged barbed wire for added interest, then other [...]]]></description>
			<content:encoded><![CDATA[<p>Sometimes, while trying to figure out FIM 2010 custom workflows, it seems to me that everyone else finds this stuff bleedingly obvious &#8211; but then I remind myself that actually, I&#8217;m not a complete idiot, and if I&#8217;m finding it all a bit like wading through chest-high treacle, with submerged barbed wire for added interest, then other people probably do too. So in case, like me, you couldn&#8217;t figure out how to use the ReadResourceActivity from the scantily provided documentation, here is what I have managed to figure out.<span id="more-927"></span></p>
<h3>What is it for?</h3>
<p>The ReadResourceActivity is used to look up an object in the FIM Portal database and return it to your workflow. You feed it a FIM GUID through the ResourceId property and it returns you a copy of the resource&#8217;s attributes in the Resource property.</p>
<p>In the pictures in this post you&#8217;ll see the word &#8220;Powershell&#8221; cropping up. In fact I&#8217;m working on an activity to run powershell cmdlets and scripts and I&#8217;ve arrived at the point where I&#8217;d like to include Target and Requestor properties in cmdlet arguments. Perhaps something like this:</p>
<p>Add-MailboxPermission -Identity [//Target/AccountName] -User [//Requestor/AccountName] -Accessright Fullaccess</p>
<p>Anyway, that&#8217;s the idea, and I&#8217;ll let you know if I get there.</p>
<h3>Adding a ReadResourceActivity</h3>
<table border="0">
<tbody>
<tr>
<td>Before dragging in the ReadResourceActvity, this Workflow had only a CurrentRequestActivity and a code activity, very much like the <a href="http://msdn.microsoft.com/en-us/library/ff859524.aspx">LoggingActivity example</a> (which I recently <a href="http://www.wapshere.com/missmiis/the-fim-2010-custom-logging-activity-in-vb-net">translated into VB.NET</a>).</p>
<p>I plan to use this activity to read properties about the Target of this workflow &#8211; that is, the object in FIM which is in the process of being added or modified. Later I will also add another ReadResourceActivity to get the properties of the Requestor.</p>
<p>In this picture, all I&#8217;ve done to the ReadResourceActivity so far is to give it a name. The other properties are the initial defaults. You can actually compile and run your activity at this point, but you&#8217;ll get a PostProcessingError in FIM.</td>
<td><img src="http://www.wapshere.com/images/customwf/readresact01.JPG" alt="" /></td>
</tr>
</tbody>
</table>
<h3>Creating DependencyProperties</h3>
<table border="0">
<tbody>
<tr>
<td>The next thing you need to do is create DependencyProperties to bind to the parameters. These are something like global variables that you can use in your code, either by writing a value in, or reading a value out.</p>
<p>The simplest thing to do here is click on the link which says <span style="text-decoration: underline;">Promote Bindable Properties</span>. This will create the DependencyProperty code for you (which you should not need to touch) and will add the bindings.</p>
<p>The one point to note here is that you lose the ability to set the list of SearchAttributes directly in this interface. If you <em>don&#8217;t</em> click <span style="text-decoration: underline;">Promote Bindable Properties</span> then, when you click on the elipses next to SelectionAttributes,  instead of the usual dialog allowing you to select or create a DependencyProperty, you get a box where you can enter the list of attributes you want returned as part of the resource loookup. In my case however I want to dynamically change the list of SelectionAttributes based on what&#8217;s been entered in the FIM Workflow, so I prefer to go with the bound property, as pictured here.</td>
<td><img src="http://www.wapshere.com/images/customwf/readresact02_PromoteBindableProperties.JPG" alt="" /></td>
</tr>
</tbody>
</table>
<h3>Set Initial Values</h3>
<table border="0">
<tbody>
<tr>
<td>I regret to say that this bit took me ABOLUTELY AGES to figure out. Before you can use the ReadResourceActivity you have to set the &#8220;input&#8221; properties:</p>
<ul>
<li>ResourceId is the GUID of the object you wish to search for,</li>
<li>ActorId is the GUID of the object doing the search &#8211; so you need to pick something with the MPR-granted rights to achieve this, and</li>
<li>SelectionAttributes is the list of attributes to return.</li>
</ul>
<p>The way I am doing this is by creating a code activity before the ReadResourceActivity. I&#8217;ll set the input properties here, the ReadResourceActivity will run, and then I&#8217;ll be able to use the resulting Resource parameter in my code that follows.</p>
<p>All I&#8217;ve done here is to drag in the Code activity and give it a name. When I double-click it the code block is automatically created and that red exclamation mark goes away. I then fill the Sub in as shown below. Note I could also set &#8220;Me.ReadTarget_ActorId1&#8243; to &#8220;containingWorkflow.ActorId&#8221; which would make the ReadResourceActivity run with the rights of the workflow requestor, but I thought it interesting to show how you can use a different account for some steps.
</td>
<td><img src="http://www.wapshere.com/images/customwf/readresact04_preCode.JPG" alt="" /></td>
</tr>
</tbody>
</table>
<pre><code>    Private Sub InitializeReadResourceActivity_ExecuteCode(ByVal sender As System.Object, ByVal e As System.EventArgs)
        '' Set the ActorID to the Built-In Administrator account
        Me.ReadTarget_ActorId1 = New Guid("7fb2b853-24f0-4498-9534-4e10589723c4")

        '' Find the TargetID and use it to set the ResourceID on the ReadTarget actvity
        Dim containingWorkflow As SequentialWorkflow = Nothing
        If Not SequentialWorkflow.TryGetContainingWorkflow(Me, containingWorkflow) Then
            Throw New InvalidOperationException("Could not get parent workflow!")
        End If
        Me.ReadTarget_ResourceId1 = containingWorkflow.TargetId

        '' Set the list of Search Attributes based on your own requirements
        Dim targetSearchAttribs As String() = Nothing
        ' Add values to the targetSearchAttribs array as required
        Me.ReadTarget_SelectionAttributes1 = targetSearchAttribs

    End Sub</code></pre>
<h3>Using the Resource</h3>
<table border="0">
<tbody>
<tr>
<td>In any code block <em>following</em> the ReadResourceActivity you can use the retrieved object via the Resource property &#8211; in this case, Me.ReadTarget _Resource1.</p>
<p>To access a particular attribute value just put it&#8217;s name in brackets as shown. Obviously, I had to include AccountName in the array Me.ReadTarget_SelectionAttributes1 when I initialized it.</td>
<td><img src="http://www.wapshere.com/images/customwf/readresact05_UsingResource.JPG" alt="" /></td>
</tr>
</tbody>
</table>
<h3>Troubleshooting</h3>
<p>Actually I&#8217;m not going to list all the errors I saw here because they were many and varied and mostly due to my complete lack of understanding about how to use this activity. Hopefully, having walked you through it here, you won&#8217; have the same problems!</p>
<p>I will mention however that the &#8220;UnwillingToPerform&#8221; error I saw a few times was connected to not setting the ActorId.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/how-to-use-the-fim-readresourceactivity-in-vb-net/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Archiving the Import and Export Logs, and viewing them with a stylesheet</title>
		<link>http://www.wapshere.com/missmiis/archiving-the-import-and-export-logs-and-viewing-them-with-a-stylesheet</link>
		<comments>http://www.wapshere.com/missmiis/archiving-the-import-and-export-logs-and-viewing-them-with-a-stylesheet#comments</comments>
		<pubDate>Mon, 16 Aug 2010 14:49:58 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[Logs]]></category>
		<category><![CDATA[VBScript]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=912</guid>
		<description><![CDATA[A long time ago I wrote up a method that could be used to archive the MIIS import and export logs, while also making them more readable with a stylesheet. I&#8217;ve now implemented this on a FIM 2010 server, and it works, so I&#8217;m going to write it up again. The Problem The FIM Sync [...]]]></description>
			<content:encoded><![CDATA[<p>A <a href="http://www.wapshere.com/missmiis/a-stylesheet-for-the-import-and-export-logs">long time ago</a> I wrote up a method that could be used to archive the MIIS import and export logs, while also making them more readable with a stylesheet. I&#8217;ve now implemented this on a FIM 2010 server, and it works, so I&#8217;m going to write it up again.<span id="more-912"></span></p>
<h3>The Problem</h3>
<p>The FIM Sync Service, just like its predecessors, only stores information about the current state of objects. Run History is almost completely worthless and <a href="http://social.technet.microsoft.com/Forums/en-US/ilm2/thread/7e60ec1f-939c-4475-bfb9-01e739b8e5fc">should be cleared regularly</a>. However there will be times when you will be asked to trace through a series of events &#8211; perhaps leading to the CEO&#8217;s account being inadvertently disabled. At times like this it is important to be able to show that FIM was just responding appropriately to an imported data change, and not acting maliciously in some skynet-esque awakening.</p>
<p>However, as we know, the import and export run profiles, while allowing a log file to be dumped, then helpfully overwrite it at the next run. We need a way to hang on to that historical data.</p>
<h3>The Proposal</h3>
<p>If you&#8217;re already running your tasks using vbscripts it&#8217;s pretty simple to add an extra step which copies the log file off to a datestamped version in an archive location (script below).</p>
<p>At the same time, we can do a little manipulation to the log file to make it more readable. By inserting a couple of lines in the top of the log file it can now be used with an XML Stylesheet, allowing it to be browsed in a nice table format.</p>
<h3>Provisos</h3>
<p>The log file will only be archived if you run your export and import jobs via your scripts. Anything run directly from the Sync Service GUI may still produce a log file, but it won&#8217;t be archived.</p>
<p>Also, the timestamp is a approximate as it represents the time the log was archived, rather than the exact time specific objects were modified in a target directory. But if you archive the log straight after the Export profile runs then it should be close enough for most purposes.</p>
<h3>log.xsl</h3>
<p>First, you need to create a folder somewhere with the same sub-folders as your MaData folder (in the script example below, I&#8217;m using D:\FIM\MALogArchives). Then, into this new folder, create a text file called &#8220;log.xsl&#8221; and paste in the following content.<br />
<code></p>
<pre>&lt;?xml version="1.0"?&gt;
&lt;xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
&lt;xsl:template match="/"&gt;
  &lt;html&gt;
  &lt;body&gt;
    &lt;h2&gt;&lt;xsl:value-of select="top/xmlfile-time" /&gt;&lt;/h2&gt;
    &lt;table border="1"&gt;
      &lt;tr bgcolor="#0066FF"&gt;
        &lt;th&gt;Operation&lt;/th&gt;
        &lt;th&gt;DN&lt;/th&gt;
        &lt;th&gt;Attributes&lt;/th&gt;
      &lt;/tr&gt;
      &lt;xsl:for-each select="top/delta"&gt;

        &lt;!-- Start a Row --&gt;
      &lt;tr&gt;

  &lt;!-- Operation and DN Columns --&gt;
         &lt;xsl:choose&gt;
         &lt;xsl:when test = "@newdn"&gt;
           &lt;td&gt;&lt;font size="2"&gt;rename&lt;/font&gt;&lt;/td&gt;
           &lt;td&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@dn" /&gt;&lt;br&gt;&lt;/br&gt;&lt;xsl:value-of select="@newdn" /&gt;&lt;/font&gt;&lt;/td&gt;
         &lt;/xsl:when&gt;
         &lt;xsl:otherwise&gt;
           &lt;td&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@operation" /&gt;&lt;/font&gt;&lt;/td&gt;
           &lt;td&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@dn" /&gt;&lt;/font&gt;&lt;/td&gt;
         &lt;/xsl:otherwise&gt;
        &lt;/xsl:choose&gt;

  &lt;!-- Attributes Column --&gt;
        &lt;td&gt;
        &lt;table border="0"&gt;

          &lt;!-- attributes --&gt;
        &lt;xsl:for-each select="dn-attr"&gt;
        &lt;tr&gt;

          &lt;!-- Multi-valued --&gt;
         &lt;xsl:if test = "@multivalued='true'"&gt;
            &lt;xsl:choose&gt;
            &lt;xsl:when test = "attr/@operation='add'"&gt;
              &lt;td bgcolor="#CCFFCC"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt; add&lt;/font&gt;&lt;/td&gt;
            &lt;/xsl:when&gt;
            &lt;xsl:when test = "attr/@operation='delete'"&gt;
              &lt;td bgcolor="#CC6666"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt; delete&lt;/font&gt;&lt;/td&gt;
            &lt;/xsl:when&gt;
            &lt;xsl:otherwise&gt;
              &lt;td bgcolor="#CCCCFF"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt;&lt;/font&gt;&lt;/td&gt;
            &lt;/xsl:otherwise&gt;
            &lt;/xsl:choose&gt;

            &lt;td&gt;
              &lt;xsl:for-each select="dn-value"&gt;
                &lt;table border="0"&gt;
                  &lt;xsl:choose&gt;
                  &lt;xsl:when test = "@operation='delete'"&gt;
                    &lt;tr&gt;&lt;td&gt;&lt;font size="2"&gt;delete: &lt;xsl:value-of select="dn" /&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;
                  &lt;/xsl:when&gt;
                  &lt;xsl:when test = "@operation='add'"&gt;
                    &lt;tr&gt;&lt;td&gt;&lt;font size="2"&gt;add: &lt;xsl:value-of select="dn" /&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;
                  &lt;/xsl:when&gt;
                  &lt;xsl:otherwise&gt;
                    &lt;tr&gt;&lt;td&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="dn" /&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;
                  &lt;/xsl:otherwise&gt;
                  &lt;/xsl:choose&gt;
                &lt;/table&gt;
              &lt;/xsl:for-each&gt;
            &lt;/td&gt;
          &lt;/xsl:if&gt;

          &lt;!-- Single-valued --&gt;
           &lt;xsl:if test = "@multivalued='false'"&gt;
             &lt;td bgcolor="#CCCCFF"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt;&lt;/font&gt;&lt;/td&gt;
             &lt;td&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="dn-value/dn" /&gt;&lt;/font&gt;&lt;/td&gt;
           &lt;/xsl:if&gt;

        &lt;/tr&gt;
        &lt;/xsl:for-each&gt;

        &lt;!-- Ordinary attributes --&gt;
        &lt;xsl:for-each select="attr"&gt;
        &lt;tr&gt;

          &lt;!-- Multi-value --&gt;
          &lt;xsl:if test = "@multivalued='true'"&gt;
            &lt;xsl:choose&gt;
            &lt;xsl:when test = "attr/@operation='add'"&gt;
              &lt;td bgcolor="#CCFFCC"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt; add&lt;/font&gt;&lt;/td&gt;
            &lt;/xsl:when&gt;
            &lt;xsl:when test = "attr/@operation='delete'"&gt;
              &lt;td bgcolor="#CC6666"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt; delete&lt;/font&gt;&lt;/td&gt;
            &lt;/xsl:when&gt;
            &lt;xsl:otherwise&gt;
              &lt;td bgcolor="#CCCCFF"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt;&lt;/font&gt;&lt;/td&gt;
            &lt;/xsl:otherwise&gt;
            &lt;/xsl:choose&gt;

            &lt;td&gt;
              &lt;xsl:for-each select="value"&gt;
                &lt;table border="0"&gt;
                  &lt;xsl:choose&gt;
                  &lt;xsl:when test = "@operation='delete'"&gt;
                    &lt;tr&gt;&lt;td&gt;&lt;font size="2"&gt;delete: &lt;xsl:value-of select="." /&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;
                  &lt;/xsl:when&gt;
                  &lt;xsl:when test = "@operation='add'"&gt;
                    &lt;tr&gt;&lt;td&gt;&lt;font size="2"&gt;add: &lt;xsl:value-of select="." /&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;
                  &lt;/xsl:when&gt;
                  &lt;xsl:otherwise&gt;
                    &lt;tr&gt;&lt;td&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="." /&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;
                  &lt;/xsl:otherwise&gt;
                  &lt;/xsl:choose&gt;
                &lt;/table&gt;
              &lt;/xsl:for-each&gt;
            &lt;/td&gt;
          &lt;/xsl:if&gt;

          &lt;!-- Single-valued --&gt;
          &lt;xsl:if test = "@multivalued='false'"&gt;
            &lt;xsl:if test = "@name!='unicodePwd'"&gt;
            &lt;xsl:if test = "@name!='msExchMailboxSecurityDescriptor'"&gt;
              &lt;td bgcolor="#CCCCFF"&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="@name" /&gt;&lt;/font&gt;&lt;/td&gt;
              &lt;td&gt;&lt;font size="2"&gt;&lt;xsl:value-of select="value" /&gt;&lt;/font&gt;&lt;/td&gt;
            &lt;/xsl:if&gt;
            &lt;/xsl:if&gt;
          &lt;/xsl:if&gt;

        &lt;/tr&gt;
        &lt;/xsl:for-each&gt;

        &lt;/table&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;/xsl:for-each&gt;
    &lt;/table&gt;
  &lt;/body&gt;
  &lt;/html&gt;

&lt;/xsl:template&gt;

&lt;/xsl:stylesheet&gt;</pre>
<p></code></p>
<h3>ArchiveLog.vbs</h3>
<p>Now here&#8217;s a vbscript that will copy the named log file, while modifying it to work with the stylesheet.</p>
<p><code>
<pre>
' This script copies the export and import logs to datestamped versions
' and modifies them to work with a stylesheet called ../log.xsl.
'
'   Usage: cscript archivelog.vbs MaName LogFileName
'
'   Eg:    cscript archivelog.vbs HR import.xml
'
' Written by Carol Wapshere

Option Explicit
Const XML_STYLESHEET = "..\log.xsl"
Const MIIS_FOLDER = "C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service"
Const ARCHIVE_FOLDER = "D:\FIM\MALogArchives"
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8
Const Unicode = -1

Dim objFS, MaName, LogName
Set objFS = CreateObject("Scripting.FileSystemObject")

If WScript.Arguments.Count &lt;&gt; 2 Then
  Usage
End If

MaName = WScript.Arguments.Item(0)
LogName = WScript.Arguments.Item(1)

ArchiveLog MaName, LogName

Sub ArchiveLog(MA, LogFile)

  Dim objLogFile, objArchiveFile
  Dim strLogName, strArchiveName, logTime, dateStamp, strLine

  strLogName = MIIS_FOLDER &#038; "\MaData\" &#038; MA &#038; "\" &#038; LogFile
  If objFS.FileExists(strLogName) Then
    logTime = Now()
    dateStamp = DatePart("yyyy", logTime) &#038; TwoChars("m", logTime) &#038;_
                                 TwoChars("d", logTime) &#038; TwoChars("h", logTime) &#038;_
                                 TwoChars("n", logTime) &#038; TwoChars("s", logTime)
    strArchiveName = ARCHIVE_FOLDER &#038; "\" &#038; MA &#038; "\" &#038; Split(LogFile,".")(0) &#038; "_" &#038; dateStamp &#038; ".xml"
    set objLogFile = objFS.OpenTextFile(strLogName, ForReading, false, Unicode)
    set objArchiveFile = objFS.OpenTextFile(strArchiveName, ForWriting, true, Unicode)
    objLogFile.ReadLine()
    objArchiveFile.WriteLine("&lt;?xml version=""1.0"" encoding=""UTF-16""?&gt;")
    objArchiveFile.WriteLine("&lt;?xml-stylesheet type=""text/xsl"" href=""" &#038; XML_STYLESHEET &#038; """?&gt;")
    objArchiveFile.WriteLine("&lt;top&gt;")
    objArchiveFile.WriteLine("&lt;xmlfile-time&gt;")
    objArchiveFile.WriteLine(logTime)
    objArchiveFile.WriteLine("&lt;/xmlfile-time&gt;")
    objLogFile.ReadLine() 'skip mmsml
    objLogFile.ReadLine() 'skip directory-entries
    strLine = objLogFile.ReadLine()
    Do Until InStr(strLine, "&lt;/directory-entries&gt;") &gt; 0
       objArchiveFile.WriteLine(strLine)
       strLine = objLogFile.ReadLine()
    Loop
    objArchiveFile.WriteLine("&lt;/top&gt;")
    objLogFile.Close()
    objArchiveFile.Close()
  End If
End Sub

Function TwoChars(dtvar, time)
  Dim i
  i = DatePart(dtvar, time)
  If i &lt; 10 Then
   TwoChars = "0" &#038; CStr(i)
  Else
   TwoChars = CStr(i)
  End If
End Function

Sub Usage
  Wscript.echo "Usage: cscript archivelog.vbs MaName import|export"
  Wscript.Quit
End Sub
</pre>
<p></code></p>
<h3>Modify the Run scripts</h3>
<p>Your last step is to modify your scheduled scripts to archive the import/export log directly after the task has run.</p>
<p><code>
<pre>
cscript AD_Export.vbs
cscript ArchiveLog.vbs "AD MA" export.xml
</pre>
<p></code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/archiving-the-import-and-export-logs-and-viewing-them-with-a-stylesheet/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The FIM 2010 Custom Logging Activity in VB.NET</title>
		<link>http://www.wapshere.com/missmiis/the-fim-2010-custom-logging-activity-in-vb-net</link>
		<comments>http://www.wapshere.com/missmiis/the-fim-2010-custom-logging-activity-in-vb-net#comments</comments>
		<pubDate>Sat, 14 Aug 2010 13:14:08 +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=894</guid>
		<description><![CDATA[Here&#8217;s how I got the Custom Workflow Logging example working in VB.NET. Please consult this post together with the Microsoft document as I&#8217;m not going to reproduce the entire thing here. The usual warnings about me being no great developer also apply. Project Naming If you want to use this as a starting point to [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s how I got the <a href="http://msdn.microsoft.com/en-us/library/ff859524.aspx">Custom Workflow Logging example</a> working in VB.NET. Please consult this post together with the Microsoft document as I&#8217;m not going to reproduce the entire thing here. The usual warnings about me being no great developer also apply.<span id="more-894"></span></p>
<h3>Project Naming</h3>
<p>If you want to use this as a starting point to writing more workflow activities then you should start by following the document <a href="http://msdn.microsoft.com/en-us/library/ee652293.aspx">How to: Create a Custom Activity Library</a>, making the following changes for VB.NET:</p>
<p>1. Obviously, wherever it says &#8220;Visual C#&#8221; or &#8220;.vc&#8221; just using the VB.NET alternatives.</p>
<p>2. The &#8220;Application&#8221; tab is different for VB.NET:</p>
<ul>
<li>For the <strong>Assembly Name</strong> I ended up with &#8221;FIM.CustomWorkflowActivitiesLibrary&#8221;</li>
<li>There is no &#8220;Default namespace&#8221; setting that I can find. Instead I set <strong>Root namespace</strong> to &#8220;FIM.CustomWorkflowActivitiesLibrary.Activities&#8221;. (This means I also had to make a change to the Namespace definition in the WebUI code, and the namespace ended up a bit different to the example document &#8211; but hey, it worked.)</li>
<li>You will find the <strong>Target Framework</strong> setting under Compile -&gt; Advanced Compile Options.</li>
</ul>
<p>For reference, my solution ends up looking like this:</p>
<p><img src="http://www.wapshere.com/images/fim_wf_logging.jpg" alt="" /></p>
<p>and the Object Browser shows these namespaces and classes:</p>
<p><img src="http://www.wapshere.com/images/fim_wf_loggingnamespaces.jpg" alt="" /></p>
<h3>Build Settings</h3>
<p>The walkthrough tells you how to add post-build steps so you don&#8217;t need to go through the process of putting your dll in the GAC and restarting FIM each time. Again the location for the modificaion is different for VB.NET. So you need to open the Solution Properties and then go to Compile -&gt; Build Events. Paste the following lines in to the &#8220;Post-build event command line&#8221; box:<br />
<code><br />
"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\gacutil.exe" /i "$(TargetPath)"<br />
net stop "Forefront Identity Manager Service"<br />
net start "Forefront Identity Manager Service"<br />
</code></p>
<h3>Adding FIM Out-of-Box Activities to the Toolbox</h3>
<p>This section is basically the same for both VC# and VB.NET.</p>
<p>Note, however, that the doc says to clear all the existing workflow actions, but then later you need the Code action to still be in the Toolbox. I wouldn&#8217;t bother clearing the existing actions &#8211; just add your FIM ones.</p>
<h3>Defining the activity sequence and properties</h3>
<p>Here&#8217;s the RequestLoggingActivity code in VB.NET.</p>
<p><code></p>
<pre>Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.IO
Imports Microsoft.ResourceManagement.WebServices.WSResourceManagement
Imports Microsoft.ResourceManagement.Workflow.Activities

Public Class RequestLoggingActivity
    Inherits SequenceActivity

#Region "Public Workflow Properties"

    Public Shared ReadCurrentRequestActivity_CurrentRequestProperty As DependencyProperty = DependencyProperty.Register("ReadCurrentRequestActivity_CurrentRequest", GetType(Microsoft.ResourceManagement.WebServices.WSResourceManagement.RequestType), GetType(RequestLoggingActivity))

    ''' &lt;summary&gt;
    '''  Stores information about the current request
    ''' &lt;/summary&gt;
    &lt;DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)&gt; _
    &lt;BrowsableAttribute(True)&gt; _
    &lt;CategoryAttribute("Misc")&gt; _
    Public Property ReadCurrentRequestActivity_CurrentRequest() As RequestType
        Get
            Return DirectCast(MyBase.GetValue(ReadCurrentRequestActivity_CurrentRequestProperty), Microsoft.ResourceManagement.WebServices.WSResourceManagement.RequestType)
        End Get
        Set(ByVal value As RequestType)
            MyBase.SetValue(RequestLoggingActivity.ReadCurrentRequestActivity_CurrentRequestProperty, value)
        End Set
    End Property

    ''' &lt;summary&gt;
    '''  Identifies the Log File Path
    ''' &lt;/summary&gt;
    Public Shared LogFilePathProperty As DependencyProperty = DependencyProperty.Register("LogFilePath", GetType(System.String), GetType(RequestLoggingActivity))
    &lt;Description("Please specify the Log File Path")&gt; _
    &lt;DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)&gt; _
    &lt;Browsable(True)&gt; _
    Public Property LogFilePath() As String
        Get
            Return DirectCast(MyBase.GetValue(RequestLoggingActivity.LogFilePathProperty), [String])
        End Get
        Set(ByVal value As String)
            MyBase.SetValue(RequestLoggingActivity.LogFilePathProperty, value)
        End Set
    End Property

    ''' &lt;summary&gt;
    '''  Identifies the Log File Name
    ''' &lt;/summary&gt;
    Public Shared LogFileNameProperty As DependencyProperty = DependencyProperty.Register("LogFileName", GetType(System.String), GetType(RequestLoggingActivity))
    &lt;Description("Please specify the Log File Path")&gt; _
    &lt;DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)&gt; _
    &lt;Browsable(True)&gt; _
    Public Property LogFileName() As String
        Get
            Return DirectCast(MyBase.GetValue(RequestLoggingActivity.LogFileNameProperty), [String])
        End Get
        Set(ByVal value As String)
            MyBase.SetValue(RequestLoggingActivity.LogFileNameProperty, value)
        End Set
    End Property
#End Region

#Region "Execution Logic"
    ''' &lt;summary&gt;
    ''' Defines to logic of the LogRequestDataToFile activity.
    ''' This code will be executed when the LogRequestDataToFile activity
    ''' becomes to active workflow.
    ''' &lt;/summary&gt;
    ''' &lt;param name="sender"&gt;&lt;/param&gt;
    ''' &lt;param name="e"&gt;&lt;/param&gt;
    ''' &lt;remarks&gt;&lt;/remarks&gt;
    Private Sub LogRequestDataToFile_ExecuteCode(ByVal sender As System.Object, ByVal e As System.EventArgs)
        'Get current request from previous activity
        Dim currentRequest As RequestType = Me.ReadCurrentRequestActivity_CurrentRequest

        Try
            ' Output the Request type and object type
            Me.Log("Request Operation: " &#038; currentRequest.Operation)
            Me.Log("Target Object Type: " &#038; currentRequest.TargetObjectType)

            ' As UpdateRequestParameter derives from CreateRequestParameter we can simplify the code by deriving
            ' from CreateRequestParameter only.
            Dim requestParameters As ReadOnlyCollection(Of CreateRequestParameter) = currentRequest.ParseParameters(Of CreateRequestParameter)()

            ' Loop through CreateRequestParameters and print out each attribute/value pair
            Me.Log("Parameters for request: " + currentRequest.ObjectID.ToString)
            For Each requestParameter As CreateRequestParameter In requestParameters
                If requestParameter.Value IsNot Nothing Then
                    Me.Log(("     " + requestParameter.PropertyName &#038; ": ") &#038; requestParameter.Value.ToString())
                End If
            Next

            Dim containingWorkflow As SequentialWorkflow = Nothing
            ' In order to read the Workflow Dictionary we need to get the containing (parent) workflow
            If Not SequentialWorkflow.TryGetContainingWorkflow(Me, containingWorkflow) Then
                Throw New InvalidOperationException("Unable to get Containing Workflow")
            End If
            Me.Log("Containing Workflow Dictionary (WorkflowData):")
            ' Loop through Workflow Dictionary and log each attribute/value pair
            For Each item As KeyValuePair(Of String, Object) In containingWorkflow.WorkflowDictionary
                Me.Log(("     " &#038; item.Key &#038; ": ") &#038; item.Value.ToString())
            Next
            Me.Log(vbLf &#038; vbLf)
        Catch ex As Exception
            Me.Log("Logging Activity Exception Thrown: " &#038; ex.Message)
        End Try
    End Sub
#End Region

#Region "Utility Functions"

    ' Prefix the current time to the message and log the message to the log file.
    Private Sub Log(ByVal message As String)
        Using log As New StreamWriter(Path.Combine(Me.LogFilePath, Me.LogFileName), True)
            'since the previous line is part of a "using" block, the file will automatically
            'be closed (even if writing to the file caused an exception to be thrown).
            'For more information see
            ' http://msdn.microsoft.com/en-us/library/yh598w02.aspx
            log.WriteLine(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + ": " &#038; message)
        End Using
    End Sub

#End Region
End Class
</pre>
<p></code></p>
<h3>Creating a User Interface for the Activity</h3>
<p>Here&#8217;s the code for this section in VB.NET. Note that I have changed the Namespace definition to get round the VB.NET &#8220;Root namespace&#8221; setting working differently to the VC# &#8220;Default namespace&#8221; setting.</p>
<p><code></p>
<pre>Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Web.UI.WebControls
Imports System.Workflow.ComponentModel
Imports Microsoft.IdentityManagement.WebUI.Controls
Imports Microsoft.ResourceManagement.Workflow.Activities
Imports FIM.CustomWorkflowActivitiesLibrary.Activities

Namespace WebUIs
    Class RequestLoggingActivitySettingsPart
        Inherits ActivitySettingsPart

        ''' &lt;summary&gt;
        ''' Called when a user clicks the Save button in the Workflow Designer.
        ''' Returns an instance of the RequestLoggingActivity class that
        ''' has its properties set to the values entered into the text box controls
        ''' used in the UI of the activity.
        ''' &lt;/summary&gt;
        Public Overrides Function GenerateActivityOnWorkflow(ByVal workflow As SequentialWorkflow) As Activity
            If Not Me.ValidateInputs() Then
                Return Nothing
            End If
            Dim LoggingActivity As New RequestLoggingActivity()
            LoggingActivity.LogFilePath = Me.GetText("txtLogFilePath")
            LoggingActivity.LogFileName = Me.GetText("txtLogFileName")
            Return LoggingActivity
        End Function

        ''' &lt;summary&gt;
        ''' Called when editing the workflow activity settings.
        ''' &lt;/summary&gt;
        Public Overrides Sub LoadActivitySettings(ByVal activity As Activity)
            Dim LoggingActivity As RequestLoggingActivity = TryCast(activity, RequestLoggingActivity)
            If LoggingActivity IsNot Nothing Then
                Me.SetText("txtLogFilePath", LoggingActivity.LogFilePath)
                Me.SetText("txtLogFileName", LoggingActivity.LogFileName)
            End If
        End Sub

        ''' &lt;summary&gt;
        ''' Saves the activity settings.
        ''' &lt;/summary&gt;
        Public Overrides Function PersistSettings() As ActivitySettingsPartData
            Dim data As New ActivitySettingsPartData()
            data("LogFilePath") = Me.GetText("txtLogFilePath")
            data("LogFileName") = Me.GetText("txtLogFileName")
            Return data
        End Function

        ''' &lt;summary&gt;
        '''  Restores the activity settings in the UI
        ''' &lt;/summary&gt;
        Public Overrides Sub RestoreSettings(ByVal data As ActivitySettingsPartData)
            If data IsNot Nothing Then
                Me.SetText("txtLogFilePath", DirectCast(data("LogFilePath"), String))
                Me.SetText("txtLogFileName", DirectCast(data("LogFileName"), String))
            End If
        End Sub

        ''' &lt;summary&gt;
        '''  Switches the activity between read only and read/write mode
        ''' &lt;/summary&gt;
        Public Overrides Sub SwitchMode(ByVal mode As ActivitySettingsPartMode)
            Dim [readOnly] As Boolean = (mode = ActivitySettingsPartMode.View)
            Me.SetTextBoxReadOnlyOption("txtLogFilePath", [readOnly])
            Me.SetTextBoxReadOnlyOption("txtLogFileName", [readOnly])
        End Sub

        ''' &lt;summary&gt;
        '''  Returns the activity name.
        ''' &lt;/summary&gt;
        Public Overrides ReadOnly Property Title() As String
            Get
                Return "Request Logging Activity"
            End Get
        End Property

        ''' &lt;summary&gt;
        '''  In general, this method should be used to validate information entered
        '''  by the user when the activity is added to a workflow in the Workflow
        '''  Designer.
        '''  We could add code to verify that the log file path already exists on
        '''  the server that is hosting the FIM Portal and check that the activity
        '''  has permission to write to that location. However, the code
        '''  would only check if the log file path exists when the
        '''  activity is added to a workflow in the workflow designer. This class
        '''  will not be used when the activity is actually run.
        '''  For this activity we will just return true.
        ''' &lt;/summary&gt;
        Public Overrides Function ValidateInputs() As Boolean
            Return True
        End Function

        ''' &lt;summary&gt;
        '''  Creates a Table that contains the controls used by the activity UI
        '''  in the Workflow Designer of the FIM portal. Adds that Table to the
        '''  collection of Controls that defines each activity that can be selected
        '''  in the Workflow Designer of the FIM Portal. Calls the base class of
        '''  ActivitySettingsPart to render the controls in the UI.
        ''' &lt;/summary&gt;
        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
            'Add a TableRow for each textbox in the UI
            controlLayoutTable.Rows.Add(Me.AddTableRowTextBox("Log File Path:", "txtLogFilePath", 400, 100, False, "Enter the log file Path."))
            controlLayoutTable.Rows.Add(Me.AddTableRowTextBox("Log File Name:", "txtLogFileName", 400, 100, False, "Enter the log file Name."))
            Me.Controls.Add(controlLayoutTable)

            MyBase.CreateChildControls()
        End Sub

#Region "Utility Functions"
        'Create a TableRow that contains a label and a textbox.
        Private Function AddTableRowTextBox(ByVal labelText As [String], ByVal controlID As [String], ByVal width As Integer, ByVal maxLength As Integer, ByVal multiLine As [Boolean], ByVal defaultValue As [String]) As TableRow
            Dim row As New TableRow()
            Dim labelCell As New TableCell()
            Dim controlCell As New TableCell()
            Dim oLabel As New Label()
            Dim oText As New TextBox()

            oLabel.Text = labelText
            oLabel.CssClass = MyBase.LabelCssClass
            labelCell.Controls.Add(oLabel)
            oText.ID = controlID
            oText.CssClass = MyBase.TextBoxCssClass
            oText.Text = defaultValue
            oText.MaxLength = maxLength
            oText.Width = width
            If multiLine Then
                oText.TextMode = TextBoxMode.MultiLine
                oText.Rows = System.Math.Min(6, (maxLength + 60) \ 60)
                oText.Wrap = True
            End If
            controlCell.Controls.Add(oText)
            row.Cells.Add(labelCell)
            row.Cells.Add(controlCell)
            Return row
        End Function

        Private Function GetText(ByVal textBoxID As String) As String
            Dim textBox As TextBox = DirectCast(Me.FindControl(textBoxID), TextBox)
            Return If(textBox.Text, [String].Empty)
        End Function

        Private Sub SetText(ByVal textBoxID As String, ByVal text As String)
            Dim textBox As TextBox = DirectCast(Me.FindControl(textBoxID), TextBox)
            If textBox IsNot Nothing Then
                textBox.Text = text
            Else
                textBox.Text = ""
            End If
        End Sub

        'Set the text box to read mode or read/write mode
        Private Sub SetTextBoxReadOnlyOption(ByVal textBoxID As String, ByVal [readOnly] As Boolean)
            Dim textBox As TextBox = DirectCast(Me.FindControl(textBoxID), TextBox)
            textBox.[ReadOnly] = [readOnly]
        End Sub
#End Region

    End Class

End Namespace
</pre>
<p></code></p>
<h3>Building the Assembly and Loading it into the FIM Portal</h3>
<p>This is exactly the same for VB.NET.</p>
<h3>Configuring the Activity in FIM</h3>
<p>This section is a little different because of the change I was forced to make to the WebUI namespace. Also, if you&#8217;ve followed my advice about using the general-purpose library name, and not a library name just for the Logging activity, then you&#8217;re going to end up with a different DLL name as well.</p>
<table>
<tbody>
<tr>
<td>Activity Name</td>
<td>FIM.CustomWorkflowActivitiesLibrary.Activities.RequestLoggingActivity</td>
</tr>
<tr>
<td>Assembly Name</td>
<td>FIM.CustomWorkflowActivitiesLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<em>xxxx</em></td>
</tr>
<tr>
<td>Type Name</td>
<td>FIM.CustomWorkflowActivitiesLibrary.Activities.WebUIs.RequestLoggingActivitySettingsPart</td>
</tr>
<tr>
<td>Is Action Activity</td>
<td>Checked</td>
</tr>
<tr>
<td>Is Authentication Activity</td>
<td>Checked</td>
</tr>
<tr>
<td>Is Authorization Activity</td>
<td>Checked</td>
</tr>
</tbody>
</table>
<h3>Troubleshooting</h3>
<p>See <a href="http://www.wapshere.com/missmiis/things-ive-been-learning-about-debugging-custom-workflows">Things I’ve been learning about debugging custom workflows</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/the-fim-2010-custom-logging-activity-in-vb-net/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Things I&#8217;ve been learning about debugging custom workflows</title>
		<link>http://www.wapshere.com/missmiis/things-ive-been-learning-about-debugging-custom-workflows</link>
		<comments>http://www.wapshere.com/missmiis/things-ive-been-learning-about-debugging-custom-workflows#comments</comments>
		<pubDate>Sat, 14 Aug 2010 11:29:13 +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=887</guid>
		<description><![CDATA[I have finally jumped into the world of FIM 2010 custom workflow activites, since the release of a good walkthrough document. Of course I decided it would be much easier to convert it all to VB.NET &#8230; which then gave me ample opportunities for troubleshooting. Thanks to Anthony Ho and Paolo Tedesco for all the [...]]]></description>
			<content:encoded><![CDATA[<p>I have finally jumped into the world of FIM 2010 custom workflow activites, since the release of <a href="http://msdn.microsoft.com/en-us/library/ff859524.aspx">a good walkthrough document</a>. Of course I decided it would be <em>much</em> easier to convert it all to VB.NET &#8230; which then gave me ample opportunities for troubleshooting. Thanks to Anthony Ho and Paolo Tedesco for all the hand-holding!<span id="more-887"></span></p>
<h3>Object Browser</h3>
<p>The first mistake I made was to get the Type Name wrong in the Activity Information Configuration (AIC) object. This turned out to be because VB.NET handles the default namespace differently to VC# and my WebUI part ended up with a completely different name than I&#8217;d expected. By using the Object Browser (opened from the View menu in Visual Studio) I could immediately see what the Type Name should have been.</p>
<p>Here&#8217;s a picture of what my project currently looks like:</p>
<p><img src="http://www.wapshere.com/images/fim_wftrshoot_objectbrowser.JPG" alt="" /></p>
<p>So when configuring my AIC for the RequestLoggingActvity I set Activity Name = &#8220;FIM.CustomWorkflowActivitiesLibrary.Activities.RequestLoggingActivity&#8221; and Type Name = &#8220;FIM.CustomWorkflowActivitiesLibrary.Activities.WebUIs.RequestLoggingActivitySettingsPart&#8221;.</p>
<p>And when configuring an AIC for the PowershellActivity I set Activity Name = &#8220;FIM.CustomWorkflowActivitiesLibrary.Activities.PowershellActivity&#8221; and Type Name = &#8220;FIM.CustomWorkflowActivitiesLibrary.Activities.WebUIs.PowershellActivitySettingsPart&#8221;.</p>
<h3>Enable Portal Trace Logging</h3>
<p>I knew how to <a href="http://msdn.microsoft.com/en-us/library/ff357801.aspx">enable trace logging for the FIM Service</a>, but until Anthony told me how, I had no idea you could get a different service trace from the Portal. Have a look at <a href="http://social.technet.microsoft.com/Forums/en-US/ilm2/thread/9f02ee34-41df-4070-bfa0-712f6c9fd0c1">this thread on the FIM forum</a> where he tells me how to enable it by editing the web.config file.</p>
<h3>Attach the VS Debugger</h3>
<p>There is an official document called <a href="http://msdn.microsoft.com/en-us/library/ff918795.aspx">Debugging Custom Activities</a> but it misses out one crucial step. I compiled my code with full Debug selected, but I kept getting &#8220;No symbols have been loaded&#8221;. The missing step is covered in this blog post: <a href="http://www.elumenotion.com/Blog/Lists/Posts/Post.aspx?ID=23">You Don&#8217;t Need to Copy PDB Files to Debug in the GAC!</a></p>
<p>The document tells you to attach to Microsoft.ResourceManagement.Service.exe to debug the activity execution part, and to w3wp.exe to troubleshoot the UI part, but doesn&#8217;t mention that there might be a number of instances of w3wp.exe and you need to get the right one. Just attach to them in turn until your breakpoints are hittable.</p>
<p>Also note, if you&#8217;re getting an Access Denied message when trying to attach the debugger you need to make sure you select Managed code. This is mentioned in the documentation, but easy to miss.</p>
<p>I also had a problem with the compile options being set to &#8220;optimized&#8221; by default. When I attached the debugger all the variables appeared to be &#8220;Nothing&#8221; and loops were skipped straight through. After wasting several days on this I figured out that I had to go into Properties -&gt; Compile -&gt; Advanced Compile Options and remove the tick next to &#8220;Enable optimizations&#8221;.</p>
<h3>Write messages into the Event Log</h3>
<p>This is just something I like to do because it&#8217;s easy to create events and it&#8217;s a good way to track errors, or indeed as a confirmation that your code has actually run. <a href="http://www.freevbcode.com/ShowCode.Asp?ID=2298">This vb.net function</a> can be used to create events.</p>
<h3>Make a simple console application that you can run directly</h3>
<p>It is more complicated to debug your code with Sharepoint and FIM in the mix so it can help to copy your code into a simpler form that you can run directly.</p>
<p>For the <strong>Activity</strong> code you could try copying it to a simple Console application, using hard-coded constants in place of the values that should come from the workflow activity definition in the Portal. Depending on the nature of your activity you may be able to run and test the code standalone before encorportaing it into your FIM activity.</p>
<p>It&#8217;s harder to test the <strong>WebUI</strong> part of the code on it&#8217;s own (at least, I don&#8217;t know how to) but you can call the WebUI part of your dll direct from a Console appliaction and run it in Visual Studio. While it doesn&#8217;t do anything it will at least show you directly any error messages.</p>
<p>In the following Console app there are two arguments to Activator.CreateInstance. The first is the Assembly Name string from the AIC, and the second is the Type Name string.</p>
<div></div>
<p><code></p>
<pre>Module Module1

    Sub Main()
        Activator.CreateInstance("Microsoft.IdentityManagement.Activities, Version=4.0.2592.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Microsoft.IdentityManagement.WebUI.Controls.PWResetActivitySettingsPart")
    End Sub

End Module</pre>
<p> </p>
<p></code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/things-ive-been-learning-about-debugging-custom-workflows/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Compiling the OpenLDAP XMA to use with FIM 2010</title>
		<link>http://www.wapshere.com/missmiis/compiling-the-openldap-xma-to-use-with-fim-2010</link>
		<comments>http://www.wapshere.com/missmiis/compiling-the-openldap-xma-to-use-with-fim-2010#comments</comments>
		<pubDate>Tue, 27 Jul 2010 12:38:38 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[OpenLDAP]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=877</guid>
		<description><![CDATA[It&#8217;s been a while since I&#8217;ve done anything with OpenLDAP. There&#8217;s no official MA but there is an open source one &#8211; however it was written for MIIS 2003 and I had to do some mucking around to get it working with the FIM 2010 Sync Service. Here is what I did. Get the Right [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s been a while since I&#8217;ve done anything with OpenLDAP. There&#8217;s no official MA but there is an open source one &#8211; however it was written for MIIS 2003 and I had to do some mucking around to get it working with the FIM 2010 Sync Service. Here is what I did.</p>
<p><span id="more-877"></span></p>
<h2>Get the Right Source Code</h2>
<p>There are actually two OpenLDAP XMAs available on sourceforge &#8211; the original Kernel Networks one, and a more recent update of it. I wasted a lot of time trying to recompile the Kernel one before I figured out that I really needed <a href="http://sourceforge.net/projects/openldap-xma">this one</a>.</p>
<p>Next &#8211; there is no point downloading the compiled version if you want to install it on FIM. You need to get the latest source code, and to do that you have to install a SVN tool (I used <a href="http://www.sliksvn.com/en/download">SlikSVN</a>) and then run this command to retrieve the source code:</p>
<p><code>svn co https://openldap-xma.svn.sourceforge.net/svnroot/openldap-xma openldap-xma</code></p>
<h2>Compile</h2>
<p>You need to have Visual Studio 2008 installed with the Visual C# options. Then you can open the OpenLDAP XMA.sln file and the project will be converted to 2008 format.</p>
<h3>Reference</h3>
<p>Next you need to change the reference to the old Microsoft. MetadirectoryServices library to the new Microsoft. MetadirectoryServicesEx library. You will file the library dll in  C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Bin\Assemblies.</p>
<h3>Build Location</h3>
<p>To have the compiled DLL put straight in the correct location, open the Properties of the OpenLDAP XMA project, click on the <strong>Build</strong> tab, and then change the <strong>Output</strong> location to the &lt;FIM Sync Service&gt;\Extensions folder.</p>
<p>You should now be able to compile the code.</p>
<h2>Register the MA Type</h2>
<p>I really couldn&#8217;t be bothered to work out how to compile the installation program, so this means I had to register the new MA type myself &#8211; but actually it&#8217;s pretty easy.</p>
<p>In the MSIPackager\Install Files folder you will find a file called <strong>OLXMAPackage.xml</strong>. Copy this file to &lt;FIM Sync Service&gt;\UIShell\XMLs\PackagedMAs.</p>
<p>Next open regedit and locate the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FIMSynchronizationService\ManagementAgents. Add a new String Value called &#8220;OpenLDAP&#8221; with the value &#8220;{275E6505-9465-4460-A6EB-D1371C863858}&#8221;. (That value is taken from the XML file we just copied &#8211; I&#8217;m not sure if it ever changes in subsequent releases.)</p>
<p>Now, when you restart the Synchronization Service Manager, you should find the new MA type listed in the Create Wizard.</p>
<h2>Testing so far</h2>
<p>I am not running this in production just yet. I have successfully connected to an OpenLDAP directory (version 3), imported existing objects and provisioned and de-provisioned an inetOrgPerson. I have not yet tried the password sync.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/compiling-the-openldap-xma-to-use-with-fim-2010/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Recovering from a failure of the installation of FIM Update 1</title>
		<link>http://www.wapshere.com/missmiis/recovering-from-a-failure-of-the-installation-of-fim-update-1</link>
		<comments>http://www.wapshere.com/missmiis/recovering-from-a-failure-of-the-installation-of-fim-update-1#comments</comments>
		<pubDate>Mon, 19 Jul 2010 14:46:35 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[FIM 2010]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=871</guid>
		<description><![CDATA[I just had Update 1 fail after it had already updated the database structure. The install &#8220;rolls back&#8221;, but it doesn&#8217;t roll back those DB changes, so leaves you with a down server. Here&#8217;s what I had to do to recover. The message was something along the lines of &#8220;account not found&#8221;, mentioning the service [...]]]></description>
			<content:encoded><![CDATA[<p>I just had Update 1 fail after it had already updated the database structure. The install &#8220;rolls back&#8221;, but it doesn&#8217;t roll back those DB changes, so leaves you with a down server. Here&#8217;s what I had to do to recover.<span id="more-871"></span></p>
<p>The message was something along the lines of &#8220;account not found&#8221;, mentioning the service account which runs the FIM Service. The account is a domain account and is certainly there. I&#8217;m afraid I didn&#8217;t write down the exact wording.</p>
<p>I did have a backup of the database but I wanted to try a reinstall first. Annoyingly the updates had dissappeared from my Windows Update list. I could see them in the update history with a status of &#8220;Failed&#8221;, but there was no way to retry the installation.</p>
<p>I had searched for a downloadable version of Update 1 before and not found it. What I have now discovered is you can download these updates direct from the <a href="http://catalog.update.microsoft.com/v7/site/Home.aspx">Microsoft Update Catalog</a>. Just search on &#8220;Forefront Identity&#8221; and there they all are.</p>
<p>So first I reinstalled the Sync Service from the original RTM files. I had to manually remove the service account from the SQL Security/Logins list. There was a warning about the database version being higher than the software I was installing, but at least the install completed.</p>
<p>I was then able to successfully install the update that I had downloaded from the Update Catalog. This time it went through perfectly with no silly messages about accounts not found.</p>
<p>I also reinstalled the Portal And Service Update (without reinstalling the RTM code). It all seems to be ok now!</p>
<h3>Addendum &#8211; reinstall of the Portal</h3>
<p>The following day the Portal had stopped working. In the event log was the message &#8220;The Portal cannot connect to the middle tier using the web service interface.&#8221; There seem to be a number of different causes for this error, and nothing I tried was making any difference. Eventually I decided to reinstall using the existing database.</p>
<p>Reinstalling just the update made no difference. I tried a repair from the RTM setup &#8211; also no change. I then completely uninstalled the Service and Portal and attempted to reinstall from the RTM setup. The install would get right to then end then say that it had &#8220;ended prematurely due to an eror&#8221;, but didn&#8217;t tell me what the error was.</p>
<p>I figured it was probably because the database had been modified by the update, but I was trying to install an earlier version of the software. Not nearly so friendly as the Sync Service installation which just gave me a polite warning. At the moment this particular system is in the early dev stage and I haven&#8217;t been given enough diskspace for more than a single backup of each DB &#8211; and of course the backup ran overnight and overwrote my pre-update version. However, after having waited two days for the initial data load, I was very keen to keep the current database.</p>
<p>What worked in the end was this: I backed up and dropped the FIMService database. I then installed RTM with a fresh DB, and tested the Portal worked. I then installed the update and tested the Portal. Finally I dropped the new DB, restored the old one, and restarted the FIM sevice. Amazingly it has worked and I now have a functioning Portal with all my data!</p>
<p>I wonder if it will still be working tomorrow&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/recovering-from-a-failure-of-the-installation-of-fim-update-1/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
