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

<channel>
	<title>missmiis &#187; ILM 2007</title>
	<atom:link href="http://www.wapshere.com/missmiis/category/ilm/ilm2007/feed" rel="self" type="application/rss+xml" />
	<link>http://www.wapshere.com/missmiis</link>
	<description>Adventures in identity management</description>
	<lastBuildDate>Fri, 10 Sep 2010 13:30:41 +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>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>Provisioning BPOS powershell commands as CS Objects</title>
		<link>http://www.wapshere.com/missmiis/provisioning-bpos-powershell-commands-as-cs-objects</link>
		<comments>http://www.wapshere.com/missmiis/provisioning-bpos-powershell-commands-as-cs-objects#comments</comments>
		<pubDate>Fri, 25 Jun 2010 05:40:27 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[powershell]]></category>

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

Public Class MACallExport
    Implements IMAExtensibleFileImport
    Implements IMAExtensibleCallExport

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

    Dim myRunSpace As Runspace
    Dim bposCred As PSCredential

    Dim fileDelta As StreamWriter

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

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

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

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

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

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

    End Sub

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

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

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

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

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

        psh.Dispose()
    End Sub

    Public Sub EndExport() Implements IMAExtensibleCallExport.EndExport

        myRunSpace.Close()

    End Sub

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=683</guid>
		<description><![CDATA[This got me out of a pickle today. A slip-up in a join rule caused hundreds of bad joins to be made. There were far too many to un-do by hand, but then I figured out I could add a few lines to the Provisioning Sub of the MVExtension to remove all the joins made [...]]]></description>
			<content:encoded><![CDATA[<p>This got me out of a pickle today. A slip-up in a join rule caused hundreds of bad joins to be made. There were far too many to un-do by hand, but then I figured out I could add a few lines to the Provisioning Sub of the MVExtension to remove all the joins made in the past hour:<br />
<code><br />
If mventry.ConnectedMAs(<em>Ma Name</em>).Connectors.Count &gt; 0 _<br />
AndAlso DateDiff(DateInterval.Hour, mventry.ConnectedMAs(<em>Ma Name</em>).Connectors.ByIndex(0).ConnectionChangeTime, Now) &lt; 1 Then<br />
  mventry.ConnectedMAs(<em>Ma Name</em>).Connectors.ByIndex(0).Deprovision()<br />
End If</code></p>
<p>Handy!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/connectionchangetime/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Account Deprovisioning Scenarios</title>
		<link>http://www.wapshere.com/missmiis/account-deprovisioning-scenarios</link>
		<comments>http://www.wapshere.com/missmiis/account-deprovisioning-scenarios#comments</comments>
		<pubDate>Tue, 26 Jan 2010 12:57:53 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[ILM 2007]]></category>
		<category><![CDATA[MIIS 2003]]></category>
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=671</guid>
		<description><![CDATA[I just posted this article in the Greatest Hits series of the ILM Technet forum. It describes some of the methods and considerations around disabling and deleting users accounts with ILM. In Identity Management, deprovisioning is every bit as important as provisioning – in fact the security guys would say it is more important. End-of-life [...]]]></description>
			<content:encoded><![CDATA[<p><!--Start Here --></p>
<p>I just posted this article in the Greatest Hits series of the ILM Technet forum. It describes some of the methods and considerations around disabling and deleting users accounts with ILM.</p>
<p><span id="more-671"></span></p>
<p>In Identity Management, deprovisioning is every bit as important as provisioning – in fact the security guys would say it is more important.</p>
<p>End-of-life management may have been one of the determining factors that got the IdM project started in the first place – while most<br />
organizations have a variety of scripts and processes they use to create accounts and assign permissions, the cleanup when a person leaves is often not handled so well.</p>
<p>General rules for object deletion have been already well covered in Markus’ article<br />
<a href="http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/603c4f8c-d782-4625-a045-009d15ed0f3b" target="_blank">Understanding Deletions in ILM</a>; however there is more that can be said on the subject of user accounts, for which an immediate delete is often not appropriate.</p>
<p>This article shows how you can use ILM to configure a flexible deprovisioning solution that is customized to your technical, organizational and compliance needs.</p>
<h2>Account Deprovisioning Scenarios in this document</h2>
<p>We are going to look at the following types of account deprovisioning:</p>
<ol>
<li>Simple deletion</li>
<li>Disabling the account</li>
<li>Deleting the account on a time-delayed basis</li>
<li>Stopping ILM from managing the account without actually deleting it (disconnection)</li>
</ol>
<h2>Parts to the Account Deprovisioning Puzzle</h2>
<p>Depending on your needs, you will most likely have to piece together a number of different elements to achieve the desired result.</p>
<p><strong>Account Disabling:</strong></p>
<p>Deactivating an object normally involves changing one or more of its attributes (eg.: setting userAccountControl on an AD user), and the usual way to do this is with an export attribute flow (EAF).</p>
<p>See code example “<em><a href="#Disabling Flow Rules">Disabling Flow Rules</a></em>” below.</p>
<p><strong>Moving deactivated accounts:</strong></p>
<ul>
<li>A common practice is to put disabled accounts in a particular place, such as a “<em>Disabled</em>” OU.For AD this is a &#8220;<em>Rename</em>&#8221; activity, and is typically done in the metaverse<br />
extension code.</li>
<li>For the “<em>Stop management</em>” scenario it is also possible to move the account just prior to disconnecting it in the MA Extension Deprovision method.</li>
</ul>
<p>See code example “<em><a href="#Metaverse Deprovisioning">Metaverse Deprovisioning</a></em>” below.<br />
 </p>
<p><strong>Deleting the connector space (CS) object:</strong></p>
<ul>
<li>The first step to deleting an account is to delete the object that represents it in the connector space.This can happen in one of the following two ways:
<ul>
<li>The joined Metaverse object is deleted</li>
<li>The joined Metaverse object was disconnected, either manually or by using the<br />
<em>CSEntry.Deprovision()</em> method in the<br />
metaverse extension code.</li>
</ul>
</li>
<li>In both cases a deletion will only happen if the &#8220;<em>Configure Deprovisioning</em>&#8221; tab on the MA configuration has been set as follows:
<ul>
<li>“<em>Stage a delete on the object for the next export run</em>”, or</li>
<li>“<em>Determine with a rules extension</em>” AND the rules extension code returns<br />
<em>DeprovisionAction.Delete</em>.</li>
</ul>
</li>
</ul>
<p> <br />
See code example “<em><a href="#MA Deprovision Sub">MA Deprovision Sub</a></em>” below<br />
 <br />
See “<em><a href="#Metaverse Deprovisioning">Metaverse Deprovisioning</a></em>” for an example of the <em>CSEntry.Deprovision()</em> method.<br />
 </p>
<p><strong>Deleting the account in the connected data source (CDS):</strong></p>
<ul>
<li>To delete the actual object in the CDS, we must first delete the connector space object using the methods above, after which we should find an export of type “<em>delete</em>” ready in the connector space.It is then just a matter of running an Export.
<ul>
<li>Note that the account used by the MA must have permission to delete objects of this type in the CDS.</li>
<li>Note also that if the MA is of type “Extensible Connectivity” you must write a Delete method in the Connected Data Source extension (see example below).</li>
</ul>
</li>
</ul>
<p> <br />
See code example “<a href="#XMA Delete Method">XMA Delete Method</a>” below.<br />
 </p>
<p><strong>Time dependant deletion:</strong></p>
<ul>
<li>Sometimes you want to delete an object on a certain date – perhaps an expiration date, or 3 months after the account was disabled.To do this you need the date available on the Metaverse object, which means you have to flow it into the Metaverse from somewhere.
<ul>
<li>An example for AD accounts is to use a spare attribute on the account, such as info or one of the extensionAttributes, to write the date at the same time as you disable the account.Flow this value back into the Metaverse and the information will be available.</li>
</ul>
</li>
</ul>
<p> <br />
See code example “<a href="#Metaverse Deprovisioning">Metaverse Deprovisioning</a>” below.<br />
 </p>
<h2>Deprovisioning Scenarios</h2>
<h3>Simple Deprovision based on disappearance</h3>
<p>The simplest deprovisioning scenario, and the one that can be executed without writing any code, is the “disappearance” scenario.</p>
<p>Here an object (or database line, or text file line) disappears from a source MA and is imported as a delete.</p>
<p>In the Metaverse we have configured our Object Deletion Rule as “<em>Delete Metaverse object when connector from this management agent is disconnected</em>” and selected our source MA.</p>
<p>Finally we have configured our other MAs to delete the CS objects when the Metaverse object is deleted.</p>
<p>In this way we could, for example, delete an AD account because the person’s record disappeared from the HR data source.</p>
<blockquote><p><strong>Caution: This method may lead to unexpected loss of accounts, temper and job!</strong></p></blockquote>
<p>As a general rule, it is a bad idea to make destructive decisions based on an absence of information. All sorts of errors, both human and machine, could happen to cause data to be unavailable at the time an Import runs. If you do use this simple approach, only use it for low-priority objects that can be deleted and recreated without much impact.</p>
<h3>Deprovision based on attribute change</h3>
<p>It is a much better practice to make deprovisioning decisions based on positive data.</p>
<p>So, with the HR data source, we continue to import resigned people, but with a status flag that indicates they are now inactive.</p>
<p>We then write some code in the metaverse extension which reacts to the person’s “inactive” status.</p>
<p>The great advantage to this method is the flexibility it gives us.</p>
<p>For example, we may deal with the person’s connected objects in different ways, deleting contacts and application accounts immediately<br />
but, just disabling the AD user account until further notice.</p>
<p>Deprovisioning a connector space object from metaverse extension code is trivial – all you have to do is add the line</p>
<p><em>CSEntry.Deprovision()</em></p>
<p>The bulk of the code before this will be spent in testing attribute values, and connections to different MAs, to determine when conditions are right to issue this command.</p>
<h3>Disable then delete</h3>
<p>It is simple enough to disable an account using an EAF (see example “<a href="#Disabling Flow Rules">Disabling Flow Rules</a>” below).</p>
<p>You could also move the account to a special location (see example “<a href="#Metaverse Deprovisioning">Metaverse Deprovisioning</a>” below).</p>
<p>However what do you do if you want to remove the account at some point in the future?</p>
<p>The first thing to be aware of is you must not delete the metaverse object.</p>
<p>To be able to delete the disabled account in the future it has to be connected to something in the<br />
metaverse.</p>
<p>ILM can only manage connectors.</p>
<p>Next, you will need some kind of <em>datestamp</em> on the metaverse object so your<br />
metaverse extension code will know when it’s OK to delete the account.</p>
<p>The only way to write a value to a metaverse object is with an IAF – so this implies writing the<br />
<em>datestamp</em> outside ILM, on a CDS object, and then importing it back in.</p>
<p>The method in the code examples below works like this:</p>
<ol>
<li>Based on the status attribute in the Metaverse, I export the <em>userAccountControl</em> to disable the AD user account,</li>
<li>Based on the same rules, I also export today’s date to the user’s info attribute,</li>
<li>II import info back to a metaverse attribute called <em>disableDate</em>,</li>
<li>I can then use <em>disableDate</em> in my metaverse extension to decide when the time is right to issue a CSEntry.Deprovision().</li>
</ol>
<p>There are a couple of points to note about this method:</p>
<ul>
<li>While disables will happen on a delta sync, deletions will only happen on a<br />
full sync.</li>
<li>The CDS attribute used to hold the date could be modified in the CDS (though you can use this to your advantage if you want to extend the disabled life of an account).</li>
</ul>
<h3>Stop Managing an Object</h3>
<p>In some cases object deletion is handled in the CDS itself and all you need to do is to stop managing the object.</p>
<p>In ILM terminology the object becomes a “disconnector” and, while it may still exist in the MA’s connector space, it is no longer connected to a Metaverse object, and ILM can no longer impact it in any way.</p>
<p>To disconnect rather than delete you actually use exactly the same CSEntry.Deprovision() method, but with the correct option selected on your MA’s Configure Deprovisioning page.</p>
<p>You could choose to:</p>
<ul>
<li>“Make them disconnectors”. The object is disconnected but remains in the CS and CDS.It will be reassessed for possible joins at each Full Sync,</li>
<li>“Make them explicit disconnectors”.Like the above, but it will not be reassessed for possible joins, or</li>
<li>Decide with a Rule Extension (see example “<a href="#MA Deprovision Sub">MA Deprovision Sub</a>” below).</li>
</ul>
<p>The “explicit” option needs a bit more discussion.</p>
<p>If ever you delete and re-import the CS all “<em>explicit</em>” tags will be lost and the objects become regular disconnectors again, and available for joins.</p>
<p>This option should only be chosen in particular circumstances, such as when there is a regular and reliable deletion of redundant objects happening in the CDS.</p>
<p>If you are sure that you will not need to rejoin to an object once it has been disconnected then another idea is to export a blocking attribute before disconnecting.</p>
<p>For example you export the string “<em>Unmanaged</em>” to an attribute on the CDS object, and only disconnect it once the attribute value is confirmed.</p>
<p>You can then use this attribute in a Connection Filter to prevent future re-connections.</p>
<h2>Some Common Problems and Questions</h2>
<h3>I staged some Deletes but I don’t want to export them now</h3>
<p>Perhaps there was an error in your code or your import data and you have a bunch of Deletes waiting to go out – but you really don’t want to do that!</p>
<p>Even after correcting the code and re-syncing you may find they turn into Delete-Adds – these should also not be exported as the actual CDS objects will be deleted and recreated – not great for AD accounts!</p>
<p>Unfortunately, the only full-proof fix in this case is a delete and re-import of the connector space.</p>
<h3>I have some old accounts with no HR reference – will ILM delete them?</h3>
<p>ILM can only delete objects that it is connected to, so if it never connected to the object it can never delete it.</p>
<p>If you plan to tidy up unmanaged objects in your CDS then have a look at the tool csexport.exe (from the MIIS\bin folder) which can be used to export lists of disconnectors.</p>
<h3>How can I block an Export if there are too many Disables?</h3>
<p>The Export run profile contains an option to stop the job if there are more than a certain number of Deletes queued to go out.</p>
<p>Unfortunately it is not possible to do something similar for disables with native functionality.</p>
<p>If something like this is needed then it should be possible to increment a count in a file from the EAF which deactivates the account, and then modify the script which runs the Export profile to first check the count.</p>
<h3>Sideways Joins</h3>
<p>A scenario you need to watch out for is what I call a “<em>sideways join</em>”.</p>
<p>Take a situation where the “<em>person</em>” object type has one source MA (eg.: HR) and multiple target MAs (AD, Notes, some other applications).</p>
<p>The source object has been deleted, but one or more of the target objects have remained and are still joined to a<br />
metaverse object.</p>
<p>The problem here is that these objects won’t show up in lists of disconnectors and so can remain, unobserved, for some time.</p>
<p>If deprovisioning logic is based on a value from the source MA then, in a default configuration, this value would have been recalled and is no longer present on the Metaverse object – so your code is probably just skipping it.</p>
<p>When writing your metaverse extension code, it is a good idea to test for unexpected situations – like an object with no connector in the primary source MA – and then throw an error or otherwise deal with the object.</p>
<h3>I can see Deletes staged in the connector space, but the objects don’t get deleted in the external system</h3>
<p>First, look for error messages in Identity Manager and in the Event Log that may indicate the problem.</p>
<p>Make sure the account used by the MA has the correct permissions in the CDS.</p>
<p>If it’s an “<em>Extensible Connectivity</em>” MA, check that a Delete method has been written in the Connected Data Source Extension.</p>
<h3>I disabled an AD user – now how do I remove it from groups?</h3>
<p>This is one with no short answer.</p>
<p>You cannot manage the <em>memberOf</em> attribute on AD users as it is a backlinked attribute.</p>
<p>So group membership can only be managed through the member attribute of groups.</p>
<p>This is fine if you are already managing AD groups with ILM – but not ok if they are managed manually in AD.</p>
<p>One of the reasons to keep an account in a disabled state for a while is to allow it to be restored quickly with all its previous rights intact – so removing it from groups may not be the best idea anyway.</p>
<p>If it is necessary then the choices are to fully take over group management with ILM, or to write a script that removes disabled users from groups, and is run outside of ILM.</p>
<h2>Code Examples</h2>
<h3>MA Deprovision Sub</h3>
<p><a name="MA Deprovision Sub">In</a> this example, when the Metaverse object is deleted or disconnected, accounts in the “<em>Student</em>” OU are deleted while accounts in the “<em>Staff</em>” OU become disconnectors.</p>
<p>This sub is located in the Management Agent Extension code.</p>
<pre lang="x-js">Public Function Deprovision(ByVal csentry As CSEntry) As DeprovisionAction Implements IMASynchronization.Deprovision
   If csentry.DN.ToString.Contains("OU=Students") Then
      Return DeprovisionAction.Delete
   ElseIf csentry.DN.ToString.Contains("OU=Staff") Then
      ' Optionally, rename the cs object or change attributes
      ' just prior to disconnecting
      Return DeprovisionAction.Disconnect
   Else
      Throw New UnexpectedDataException("DN does not contain " _
      &amp; "'OU=Staff' or 'OU=Students' so I don't know " _
      &amp; "which deprovision action to perform.")
   End If
End Function</pre>
<h3>Metaverse Deprovisioning</h3>
<p><a name="Metaverse Deprovisioning">In</a> this example we use the user’s status attribute in the Metaverse to decide if the account should be moved to a “<em>Disabled</em>” OU.</p>
<p>(The actual account disabling is done by an EAF and the <em>disableDate</em> and<br />
<em>userAccountControl</em> are flowed back by IAFs – see below.)</p>
<p>We then use the <em>disableDate</em> attribute on the metaverse object to decide when to perform the final deletion.</p>
<p>This example subroutine has been called from <em>Sub Provision</em> which is located in the Metaverse Extension code.</p>
<pre lang="x-js">Private Sub User_Provisioning(ByVal mventry As MVEntry)
   Dim ADMA As ConnectedMA = mventry.ConnectedMAs("MyDomain")
   Dim expectedDN As ReferenceValue
   Dim ShouldExist As Boolean
   Dim DoesExist As Boolean
   Const OU_USERS As String = "OU=Users,OU=MyOrg,DC=mydomain,DC=com"
   Const OU_DISABLED As String = "OU=Disabled,OU=Users,OU=MyOrg,DC=mydomain,DC=com"
   Const KEEP_DISABLED_DAYS As Integer = 90

   '' Should the account exist?
   '' Inactive accounts should exist for KEEP_DISABLED_DAYS after being disabled.
   If mventry("status").IsPresent AndAlso mventry("status").StringValue = "Active" Then
      ShouldExist = True
   Else
      ShouldExist = False

      If MVEntry("userAccountControl").IsPresent AndAlso _
         MVEntry("disableDate").IsPresent Then

         If (MVEntry("userAccountControl").IntegerValue And ADS_UF_ACCOUNTDISABLE) = _
             ADS_UF_ACCOUNTDISABLE Then

            'Account disabled – allow to exist until deletion date
            ShouldExist = True

            Dim disabledDate As DateTime
            disabledDate = Convert.ToDateTime(MVEntry("disableDate").StringValue)
            If Now.Subtract(disabledDate).Days &gt; KEEP_DISABLED_DAYS Then
               ShouldExist = False
            End If
         Else
            'Account enabled
            ShouldExist = True
         End If
      End If
   End If
   '' Check if the AD account already exists
   Select Case ADMA.Connectors.Count
      Case 0
         DoesExist = False
      Case 1
         DoesExist = True
      Case Else
         Throw New UnexpectedDataException("Multiple connectors in MA " &amp; ADMA.Name)
   End Select

   '' Generate the expected DN for the user - to use in renaming or moving
   Dim RDN As String = "CN=" &amp; mventry("displayName").StringValue

   If mventry("status").StringValue = "Active" Then
      expectedDN = ADMA.EscapeDNComponent(RDN).Concat(OU_USERS)
   Else
      expectedDN = ADMA.EscapeDNComponent(RDN).Concat(OU_DISABLED)
   End If

   '' Take action based on values of ShouldExist and DoesExist

   If ShouldExist And DoesExist Then
      'Check if account should be renamed or moved
      Dim CSEntry As CSEntry = ADMA.Connectors.ByIndex(0)
      If CSEntry.DN.ToString.ToLower &lt;&gt; expectedDN.ToString.ToLower Then
         CSEntry.DN = expectedDN
      End If

   ElseIf ShouldExist And Not DoesExist Then
      'Provision Account
      &lt;...&gt;

   ElseIf Not ShouldExist And DoesExist Then
      'Deprovision Account
      CSEntry.Deprovision()

   End If
 End Sub</pre>
<h3>Disabling Flow Rules</h3>
<p><a name="Disabling Flow Rules">With</a> these flow rules I disable an AD account and also write a date onto the object (I’m using info but you can use any free attribute) to indicate when it was disabled.</p>
<p>I also flow <em>userAccountControl</em> and info back into the metaverse so I have access to the values in my<br />
metaverse extension code (above).</p>
<p><img src="http://public.bay.livefilestore.com/y1p--wnV_K-shH6tRb6q_v9QW6CK8ju5SvYrGmyGW6MnVhzsHhYziWODyCHvP4kcrnF3HUmReTUud6NEi7hpFdUXw/DP01.jpg" alt="" width="572" height="234" /></p>
<pre lang="x-js">Public Sub MapAttributesForExport(ByVal FlowRuleName As String, ByVal mventry As MVEntry, ByVal csentry As CSEntry) Implements IMASynchronization.MapAttributesForExport

   Const ADS_UF_NORMAL_ACCOUNT As Long = &amp;H200
   Const ADS_UF_ACCOUNTDISABLE As Long = &amp;H2

   Select Case FlowRuleName

      Case "export_userAccountControl"
         Dim currentValue As Long

         If csentry("userAccountControl").IsPresent Then
            currentValue = csentry("userAccountControl").IntegerValue
         Else
            currentValue = ADS_UF_NORMAL_ACCOUNT
         End If

         If mventry("status").IsPresent AndAlso mventry("status").Value = "Active" Then
            ' Enable account
            csentry("userAccountControl").IntegerValue = _
            (currentValue Or ADS_UF_NORMAL_ACCOUNT) And (Not ADS_UF_ACCOUNTDISABLE)

         Else
            ' Disable account
            csentry("userAccountControl").IntegerValue = _
            currentValue Or ADS_UF_ACCOUNTDISABLE
         End If

      Case "export_info"
         If mventry("status").IsPresent AndAlso mventry("status").Value = "Active" _
            AndAlso csentry("info").IsPresent Then
            csentry("info").Delete()
         ElseIf mventry("status").Value = "Inactive" AndAlso _
            Not csentry("info").IsPresent Then
            csentry("info").StringValue = Now.ToString
         End If

      End Select
End Sub</pre>
<h3>XMA Delete Method</h3>
<p><a name="XMA Delete Method">For</a> an MA of type “<em>Extensible Connectivity</em>” you need to write your own routines for the export types<br />
<em>Add</em>, <em>Modify</em> and <em>Delete</em>.</p>
<p>This example shows the <em>Delete</em> step for an XMA which manages home folders for user accounts.</p>
<p>The sub is found in the <em>Connected Data Source Extension</em>.</p>
<p>This example is a very simple deletion of the folder, but you could easily add extra code to, for example, move the folder to an archive location.</p>
<p>(If you want to see an example of the Add method see my blog:<br />
<a href="http://www.wapshere.com/missmiis/creating-user-home-directories-windows-version" target="_blank">Creating Home Directories</a>)</p>
<pre lang="x-js"> Public Sub ExportEntry(ByVal modificationType As ModificationType, ByVal changedAttributes As String(), ByVal csentry As CSEntry) Implements IMAExtensibleCallExport.ExportEntry

If modificationType = Microsoft.MetadirectoryServices.ModificationType.Add Then
' Create the folder

ElseIf modificationType = _
Microsoft.MetadirectoryServices.ModificationType.Delete Then
System.IO.Directory.Delete(csentry("path").StringValue, True)
End If

End Sub</pre>
<h2>About the Author</h2>
<p>Carol Wapshere has been working in IT since 1990, and has since worked in many different organizations, across four different countries. She started out in Netware then moved into Microsoft server products, picking up an assortment of skills in other non-Microsoft systems along the way. She first started working with MIIS in 2005 and loved how it could be used to tie together disparate systems, bringing in much-needed order, and making lots of tedious jobs just disappear.</p>
<p>Thanks to Markus Vilcinskas and Peter Geelen for their help with this document.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/account-deprovisioning-scenarios/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Group Management Comparison</title>
		<link>http://www.wapshere.com/missmiis/group-management-comparison</link>
		<comments>http://www.wapshere.com/missmiis/group-management-comparison#comments</comments>
		<pubDate>Thu, 31 Dec 2009 08:34:11 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[Exchange 2010]]></category>
		<category><![CDATA[FIM 2010]]></category>
		<category><![CDATA[Groups]]></category>
		<category><![CDATA[ILM 2007]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=655</guid>
		<description><![CDATA[Following on from my last post about the overlaps between FIM and Exchange 2010 I wanted to clarify for myself the group management capabilities in FIM, Exchange 2010 and ILM. Warning: I will have to revisit this post &#8211; as I haven&#8217;t yet installed Exchange 2010 in a production environment the Exchange comments are based [...]]]></description>
			<content:encoded><![CDATA[<p>Following on from my <a href="http://www.wapshere.com/missmiis/is-there-too-much-overlap-between-fim-2010-and-exchange-2010">last post</a> about the overlaps between FIM and Exchange 2010 I wanted to clarify for myself the group management capabilities in FIM, Exchange 2010 and ILM.</p>
<blockquote><p>Warning: I will have to revisit this post &#8211; as I haven&#8217;t yet installed Exchange 2010 in a production environment the Exchange comments are based on reading rather than hands-on experience, and in particular I&#8217;m unsure about the management of email-enabled Security groups.<br />
<span id="more-655"></span></p></blockquote>
<table border="2">
<thead>
<tr>
<th colspan="4">Exchange Distribution Groups</th>
</tr>
</thead>
<tbody>
<tr>
<td width="10%"> </td>
<td width="30%"><strong>FIM 2010</strong></td>
<td width="30%"><strong>Exchange 2010</strong></td>
<td width="30%"><strong>ILM 2007<br />
or FIM Sync</strong></td>
</tr>
<tr>
<td>Rules based<br />
eg., &#8220;All Finance Dept&#8221;</td>
<td>Managed and populated in FIM Portal.</p>
<ul>
<li>All users must also be represented in FIM Portal.</li>
</ul>
</td>
<td>Dynamic Distribution Lists</td>
<td>External genarator needed<br />
Eg., Group Populator</td>
</tr>
<tr>
<td>Manually populated</td>
<td>Managed and populated in FIM Portal.</p>
<ul>
<li>Owner approval workflow,</li>
<li>Join and approve in Outlook,</li>
<li>Create, join and approve in FIM Portal.</li>
</ul>
</td>
<td>Managed and populated directly in AD</p>
<ul>
<li>Owner approval workflow,</li>
<li>Request and approve in Outlook,</li>
<li>Create in ECP.</li>
</ul>
</td>
<td>No native functionality to permit manual group popluation.<br />
Synchronizes membership lists from one system to another (eg., from a database table to AD).</td>
</tr>
<tr>
<td>Other Management</td>
<td>
<ul>
<li>Manage other attributes, eg., who can send to the list,</li>
<li>Auto-generate new groups, eg., for a new Department or Location (with Workflow development).</li>
</ul>
</td>
<td>Exchange management tools.</td>
<td>Synchronize any attribute, though it must be generated somewhere outside ILM.</td>
</tr>
<tr>
<td>Access Control</td>
<td>Permissions to create, delete, modify granted in FIM Portal only.</td>
<td>Permissions to create, delete, modify granted in AD &#8211; though RBAC simplifies.</td>
<td>N/A</td>
</tr>
</tbody>
</table>
<p> <br />
 </p>
<table border="2">
<thead>
<tr>
<th colspan="4">Security Groups</th>
</tr>
</thead>
<tbody>
<tr>
<td width="10%"> </td>
<td width="30%"><strong>FIM 2010</strong></td>
<td width="30%"><strong>Exchange 2010</strong></td>
<td width="30%"><strong>ILM 2007<br />
or FIM Sync</strong></td>
</tr>
<tr>
<td>Rules based<br />
eg., &#8220;All Finance Dept&#8221;</td>
<td>Populated and managed in FIM Portal.</p>
<ul>
<li>All users must also be represented in FIM Portal.</li>
</ul>
</td>
<td>N/A</td>
<td>External genarator needed<br />
Eg., Group Populator</td>
</tr>
<tr>
<td>Manually populated</td>
<td>Managed and populated in FIM Portal.</p>
<ul>
<li>Owner approval workflow,</li>
<li>Join and approve in Outlook <em>only if the group has an email address</em>,</li>
<li>Create, join and approve in FIM Portal.</li>
</ul>
</td>
<td>Membership management for Security groups with email address?</p>
<p>N/A for non-email emabled Security groups.</td>
<td>No native functionality to permit manual group popluation.<br />
Synchronizes membership lists from one system to another (eg., from a database table to AD).</td>
</tr>
<tr>
<td>Other Management</td>
<td>
<ul>
<li>Replicate the groups to other systems &#8211; not just AD,</li>
<li>Auto-generate new groups, eg., for a new Department or Location (with Workflow development).</li>
</ul>
</td>
<td>Mail-enable existing AD Security groups using Exchange management tools.</td>
<td>Replicate the groups to other systems &#8211; not just AD.</td>
</tr>
<tr>
<td>Access Control</td>
<td>Permissions to create, delete, modify granted in FIM Portal only.</td>
<td>N/A</td>
<td>N/A</td>
</tr>
</tbody>
</table>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/group-management-comparison/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Managing Exchange 2000/2003/2007 with ILM 2007</title>
		<link>http://www.wapshere.com/missmiis/managing-exchange-200020032007-with-ilm-2007</link>
		<comments>http://www.wapshere.com/missmiis/managing-exchange-200020032007-with-ilm-2007#comments</comments>
		<pubDate>Sun, 08 Nov 2009 14:23:51 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[Exchange 2003]]></category>
		<category><![CDATA[Exchange 2007]]></category>
		<category><![CDATA[ILM 2007]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=579</guid>
		<description><![CDATA[I have just posted this article in the Greatest Hits series on the ILM Technet forum. http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/f8ad045d-7252-4cd1-a189-d704a8f99129 The article covers various management tasks you can acheive with the standard AD MA, including provisioning and updating of users, mailboxes, contacts and distribution groups. There are quite a few code samples as well. Managing Exchange 2000/2003/2007 with ILM [...]]]></description>
			<content:encoded><![CDATA[<p>I have just posted this article in the Greatest Hits series on the ILM Technet forum.</p>
<p><a href="http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/f8ad045d-7252-4cd1-a189-d704a8f99129">http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/f8ad045d-7252-4cd1-a189-d704a8f99129</a></p>
<p>The article covers various management tasks you can acheive with the standard AD MA, including provisioning and updating of users, mailboxes, contacts and distribution groups. There are quite a few code samples as well.<span id="more-579"></span></p>
<h2>Managing Exchange 2000/2003/2007 with ILM 2007</h2>
<p>This article covers the management of Exchange-enabled objects using the native Active Directory Management Agent that is included with ILM 2007 FP1.</p>
<p>The managed object types discussed are Users, Contacts, Groups and Dynamic Distribution Lists. The article also covers the special cases of adding mailboxes to existing accounts, and supporting a Resource Forest. Where extra steps are required for Exchange 2007 this has been highlighted.</p>
<p>It is assumed that the reader is comfortable with the concepts of Provisioning code and Advanced attribute flow rules.</p>
<h2 style="color: #365f91;">Permissions</h2>
<p>The service account used in the connection properties of the Management Agent must have sufficient rights to execute the required changes in AD.</p>
<p>Typically a Domain Admin account will be used, but if this is not permitted in your environment you will need to do some testing. The minimum permissions required are:</p>
<ul>
<li><a href="http://support.microsoft.com/kb/303972">Replicate Directory Changes</a></li>
<li>Rights to create/delete/modify objects in the specific OUs</li>
<li>Exchange Administrator (2003) or Exchange Recipient Administrator (2007)</li>
</ul>
<p> </p>
<h2 style="color: #365f91;">Users</h2>
<h3 style="color: #365f91;">Provisioning Mail Users</h3>
<h4 style="color: #365f91;">Exchange 2000/2003</h4>
<p>Provisioning a mail user is most simply done using the <a href="http://msdn.microsoft.com/en-us/library/ms696038(VS.85).aspx">CreateMailbox</a> method of the <a href="http://msdn.microsoft.com/en-us/library/ms696074(VS.85).aspx">ExchangeUtils</a> class. This method will create a new user account, and populate the necessary mail attributes for you.</p>
<p>See the code sample <span style="text-decoration: underline;">Create a User with a Mailbox</span> at the end of this document for an example of the provisioning code.</p>
<h4 style="color: #365f91;">Mixed Exchange 2003 and 2007</h4>
<p>In a mixed environment the RUS still runs so Exchange 2003 methods may be used. Make sure that you do <strong>not</strong> tick the “Enable Exchange 2007 provisioning” box in the Management Agent configuration.</p>
<h4 style="color: #365f91;">Exchange 2007</h4>
<p>The same code will work when provisioning to Exchange 2007, however there are some extra requirements for the ILM server:</p>
<ul>
<li>ILM 2007 FP1 or later</li>
<li>Powershell</li>
<li>Exchange 2007 Management Tools</li>
<li>Latest rollup packs on Exchange and ILM servers</li>
</ul>
<p>In addition you must tick Enable Exchange 2007 provisioning on the Extensions tab of the Management Agent.</p>
<h4 style="color: #365f91;">Adding a Mailbox to an existing User</h4>
<p>Sometimes you may need to create a mailbox for an existing account. As the account already exists this is not actually a provisioning task, and is therefore handled with export flow rules.</p>
<p>All you need to do is to populate the following attributes, in addition to the basic user attributes:</p>
<ul>
<li>displayName – if not already set</li>
<li>mailNickname – with the local part of the email address (the bit before the “@”)</li>
<li>homeMDB – with the DN of the mail store</li>
<li>mDBUseDefaults – set to “True” to use the default quota settings</li>
</ul>
<p> </p>
<h3 style="color: #365f91;">Special Mailbox Types</h3>
<p>Exchange 2007 includes some extra mailbox types:</p>
<ul>
<li>Room Mailbox,</li>
<li>Equipment Mailbox,</li>
<li>Linked Mailbox.</li>
</ul>
<p>The Linked Mailbox is covered in the <span style="text-decoration: underline;">Resource Forest</span> section below.</p>
<p>The Room and Equipment mailboxes are currently not supported by ILM 2007 provisioning. The only reliable method is to create a User Mailbox using ILM 2007, and then use the set-mailbox cmdlet to change the mailbox type.</p>
<h4 style="color: #365f91;">Troubleshooting</h4>
<h5 style="color: #365f91;">Export Errors</h5>
<p>The most common problems with provisioning Exchange users will relate to permissions. Make sure that the account used by the MA to connect to AD has permission to create Exchange users. Also make sure you have the latest service packs and rollups on the Exchange and ILM servers – at least SP1 RU9.</p>
<h5 style="color: #365f91;">Where’s the Mailbox?</h5>
<p>Exchange does not create the actual mailbox until it is opened or something is sent to it, therefore it is completely normal for no new mailboxes to be listed directly after the ILM export.</p>
<p>To confirm if the user is really mail-enabled:</p>
<ul>
<li>In Exchange 2003, check that the user’s Exchange tabs have appeared in the Exchange-enhanced version of AD Users &amp; Computers.</li>
<li>In Exchange 2007, use the get-user cmdlet to confirm the user’s object type is “UserMailbox”, or check that they appear as a Recipient in the Management Console.</li>
</ul>
<p> </p>
<h5 style="color: #365f91;">Exchange 2007 and Global Catalog targeting</h5>
<p>There is a known problem with Exchange 2007 provisioning and AD replication delays. On the MA’s Configure Directory Partitions tab you can hard-code the name of a preferred domain controller. Enter the name of the nearest Global Catalog to ensure that both the user creation and the mailbox creation are performed in the same place.</p>
<table style="width: 100%;" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<th style="text-align: left; background-color: #c0c0c0;"><img src="http://apfhrw.bay.livefilestore.com/y1pCed6u9dxDV3LOdzgtYt8xFaDWmIf_thMiOiFb3SmUARdxwIei5b6sPCHGruZWYphrJEU8j2BZEd51ZoAkp_ONkG8moMODvLC/Note.gif" alt="note" />Note</th>
</tr>
<tr>
<td style="background-color: #f0f0f0;">Use the Resource Kit utility nltest to find Global Catalog servers:<br />
<em>nltest /DSGETDC:mydomain.com /GC</em></td>
</tr>
</tbody>
</table>
<p> </p>
<h3 style="color: #365f91;">Modifying Mail Users</h3>
<p>You can change a user’s Exchange related attributes using export flow rules.</p>
<p>The following table is not exhaustive. If you wish to automate an Exchange modification the best thing to do is make the change manually and then inspect the attribute changes using ADSIEdit.<br />
In this way you can discover which attributes you need to create flow rules for, and the types of value you should flow.</p>
<table border="0">
<tbody>
<tr>
<th style="text-align: center; background-color: #d4d0c8; padding-left: 5px; padding-right: 5px; font-weight: bold;">Attribute</th>
<th style="text-align: center; background-color: #d4d0c8; padding-left: 5px; padding-right: 5px; font-weight: bold;">Function</th>
<th style="text-align: center; background-color: #d4d0c8; padding-left: 5px; padding-right: 5px; font-weight: bold;">Comments</th>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">altRecipient</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Used in forwarding – the DN or the mail-enabled object to forward all mail to.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">When forwarding mail to an external account you must create a Contact object in this Exchange organization.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">deliverAndRedirect</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">If forwarding is enabled, set to TRUE to deliver to both the mailbox and the forwarding address.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Use in combination with altRecipient.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">extensionAttributen</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Free-use string attributes where you can store any data you like.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Be consistent. If extensionAttribute4 is being used for star sign, then make sure it is only ever used for that.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">homeMDB</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Location of the mailbox.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Do NOT change once it has been set. If you need to move the mailbox use Exchange admin utilities.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">mail</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The user’s primary email address.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">If changing the address you should also change mailNickname and the “SMTP:” value of proxyAddresses.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">mailNickname</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">This should match the local part of the primary email address.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">mDBUseDefaults</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Use the default quota for the mail store.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Set to False if setting an individual limit.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">mDBStorageQuota</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The “Warning” limit Expressed in Kbytes.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">mDBOverQuotaLimit</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The “Block Send” limit Expressed in Kbytes.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">mDBOverHardQuotaLimit</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The “Block Send &amp; Receive” limit</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Expressed in Kbytes.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">msExchHideFromAddressLists</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Set to “True” to hide from the GAL.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">msExchMailboxGuid</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The unique identifier of the mailbox</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">DON’T CHANGE THIS! It can be useful to flow this back into the Metaverse if you need to test that the mailbox was created.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">proxyAddresses</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Multivalue attribute holding all possible email addresses for this account</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The capital “SMTP:” address is the primary. The other “smtp:” addresses act as aliases.</td>
</tr>
</tbody>
</table>
<p> </p>
<h3 style="color: #365f91;">Resource Forest</h3>
<p>In a Resource Forest scenario the following accounts are needed:</p>
<ol>
<li>An enabled user account in the Account Forest.</li>
<li>A disabled account in the Resource Forest with an attached mailbox.</li>
</ol>
<p>The account creation in the two forests and the mailbox linking are simple enough to achieve with ILM. A provisioning code sample has been included at the end of this document under <span style="text-decoration: underline;">Create Account Forest and Resource Forest Accounts</span>.</p>
<p>The difficulty comes with the permissions assignment piece of the puzzle – it is necessary for the user’s account to have the <strong>Full Access</strong> and <strong>Send As</strong> rights to the mailbox. This is not something that is possible with the native Active Directory MA.</p>
<p>While there are several ways to solve the permissions-assignment problem, the typical way is to run a script after the export step. The script might simply trawl AD looking for accounts to update or it could read details from the ILM export log and target the new accounts.</p>
<p>While outside the scope of this document, the following resources have been included for reference:</p>
<ol>
<li>A <a href="http://support.microsoft.com/kb/310866/en-us">Microsoft technote</a> showing how to Script Exchange 2000/2003 mailbox permissions,</li>
<li>A PowerShell script for Exchange 2007 has been included in the Code section at the end of this article.</li>
</ol>
<p> </p>
<h2 style="color: #365f91;">Contacts</h2>
<p>Contacts are used for two primary functions in Exchange, both of which can be automated with ILM:</p>
<ol>
<li>Adding organization-wide contacts to the Global Address List.<br />
ILM could be used to import information from a CRM system and automatically create the contact object.</li>
<li>As a way to forward mail from a mailbox within the organization.<br />
Some organizations (such as universities) allow users to forward their mail to another address. As long as ILM has the information about the forwarding request (perhaps entered by the user in a self-service portal) it can be configured to create the contact and set up the forwarding.</li>
</ol>
<h3 style="color: #365f91;">Provisioning</h3>
<p>Contacts may be provisioned very simply using the <a href="http://msdn.microsoft.com/en-us/library/ms696036(VS.85).aspx">CreateMailEnabledContact</a> method from the ExchangeUtils class.<br />
See the code sample <span style="text-decoration: underline;">Create a Contact</span> at the end of this document for an example of the provisioning code.</p>
<h3 style="color: #365f91;">Modifying</h3>
<table border="0">
<tbody>
<tr>
<th style="text-align: center; background-color: #d4d0c8; padding-left: 5px; padding-right: 5px; font-weight: bold;">Attribute</th>
<th style="text-align: center; background-color: #d4d0c8; padding-left: 5px; padding-right: 5px; font-weight: bold;">Function</th>
<th style="text-align: center; background-color: #d4d0c8; padding-left: 5px; padding-right: 5px; font-weight: bold;">Comments</th>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">mail</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The contact’s email address.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">If changing the address you should also change targetAddress and the “SMTP:” value of proxyAddresses.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">msExchHideFromAddressLists</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Set to “True” to hide from the GAL.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;"> </td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">proxyAddresses</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">Multivalue attribute holding all possible email addresses that will forward via the contact.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">To work the contact needs an alias using “smtp:” in the local domain. The “SMTP:” address should match mail and targetAddress.</td>
</tr>
<tr>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">targetAddress</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">The email address that mail sent to this contact will be forwarded to.</td>
<td style="text-align: left; padding-left: 5px; padding-right: 5px;">One address only.</td>
</tr>
</tbody>
</table>
<p> </p>
<h2 style="color: #365f91;">Distribution List</h2>
<p>There are three types of Distribution list in Exchange:</p>
<ol>
<li>Groups of type Distribution</li>
<li>Groups of type Security that have an email address</li>
<li>Dynamic distribution lists.</li>
</ol>
<p>All three types can be created and managed with ILM, but the processes will differ.</p>
<h3 style="color: #365f91;">Distribution Groups</h3>
<p>To provision a standard Distribution Group use the <a href="http://msdn.microsoft.com/en-us/library/ms696024(VS.85).aspx">CreateDistributionList</a> method of the <a href="http://msdn.microsoft.com/en-us/library/ms696074(VS.85).aspx">ExchangeUtils</a> class. See <span style="text-decoration: underline;">Create a Distribution List</span> at the end of this document for a code sample.</p>
<p>The main modification you will do with groups is to update the membership list. Group population is outside the scope of this document, though it is worth looking into Group Populator and Multi-Value tables.</p>
<h3 style="color: #365f91;">Security Groups with Email Address</h3>
<p>It is possible to mail-enable a Security group, allowing it to then also act as a distribution list.</p>
<p>Provisioning such a group is a simple matter of creating a security group and adding the mail address. See <span style="text-decoration: underline;">Create a Mail-Enabled Security Group</span> under Code Samples at the end of this document.</p>
<h3 style="color: #365f91;">Dynamic Distribution Lists</h3>
<p>You may also use ILM to provision Dynamic Distribution Lists. All you need to do is to create an object of type msExchDynamicDistributionList and add values to the following attributes:</p>
<ul>
<li>displayName</li>
<li>mailNickname</li>
<li>msExchDynamicDLFilter</li>
<li>msExchDynamicDLBaseDN</li>
</ul>
<p>See <span style="text-decoration: underline;">Create a Dynamic Distribution List</span> under Code Samples at the end of this document.</p>
<h2 style="color: #365f91;">Code Samples</h2>
<h3 style="color: #365f91;">Create a User with a Mailbox</h3>
<p> </p>
<p>This MVExtension code is in addition to export flow rules to the user object type on the following attributes:</p>
<ul>
<li>displayName</li>
<li>givenName</li>
<li>sAMAccountName</li>
<li>sn</li>
<li>userPrincipalName</li>
</ul>
<p> </p>
<pre>Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision

  Const ADS_UF_NORMAL_ACCOUNT As Integer = &amp;H200

  Dim csentry As CSEntry

  Dim MA As ConnectedMA

  Dim dn As ReferenceValue

  Dim rdn As String

  Dim homeMDB As String

  Dim mailNickname As String

  Dim mail As String

  Select Case mventry.ObjectType

  Case "person"

    MA = mventry.ConnectedMAs("MYDOMAIN")

    If &lt;test that account should exist&gt; AndAlso MA.Connectors.Count = 0 Then

      rdn = "CN=" &amp; mventry("sn").Value &amp; ", " &amp; mventry("givenName").Value

      dn = MA.EscapeDNComponent(rdn).Concat("OU=Users,OU=MyOrg, " _

                                            &amp; "dc=mydomain,dc=local")

      mailNickname = mventry("mailNickname").Value

      ' The following line assumes MDB, SG and MailServer have been

      ' populated for the user in the Metaverse.

      homeMDB = "CN=&amp; mventry("MDB").StringValue _

         &amp; ",CN=" &amp; mventry("SG").StringValue _

         &amp; ",CN=InformationStore,CN=" &amp; mventry("MailServer").StringValue _

         &amp; ",CN=Servers,CN=Exchange Administrative Group (FYDIBOHF23SPDLT)" _

         &amp; ",CN=Administrative Groups,CN=First Organization" _

         &amp; ",CN=Microsoft Exchange,CN=Services,CN=Configuration" _

         &amp; ",DC=mydomain,DC=local"  

      csentry = ExchangeUtils.CreateMailbox(MA, dn, mailNickname, homeMDB)

      csentry.DN = dn

      csentry("unicodePwd").Values.Add("FirstP@ssw0rd")

      csentry("userAccountControl").IntegerValue = ADS_UF_NORMAL_ACCOUNT

      csentry.CommitNewConnector()

    End If

  End Select

End Sub</pre>
<p> </p>
<h3 style="color: #365f91;">Create Account Forest Accounts and Resource Forest Accounts</h3>
<p> </p>
<pre>Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision

  Const ADS_UF_NORMAL_ACCOUNT As Integer = &amp;H200

  Dim csentry As CSEntry

  Dim MA As ConnectedMA

  Dim dn As ReferenceValue

  Dim rdn As String

  Dim homeMDB As String

  Dim mailNickname As String

  Dim mail As String

  Select Case mventry.ObjectType

  Case "person"

    'Create Account Forest account - no mailbox

    MA = mventry.ConnectedMAs("AccountForest")

    If MA.Connectors.Count = 0 Then

      rdn = "CN=" &amp; mventry("sn").StringValue _

                  &amp; ", " &amp; mventry("givenName").StringValue

      dn = MA.EscapeDNComponent(rdn).Concat("OU=Users,OU=MyOrg, " _

                                            &amp; "dc=accountdomain,dc=local")

      csentry = MA.Connectors.StartNewConnector("user")

      csentry.DN = dn

      csentry("unicodePwd").Values.Add("FirstP@ssw0rd")

      csentry("userAccountControl").IntegerValue = ADS_UF_NORMAL_ACCOUNT

      csentry.CommitNewConnector()

    End If

    'Create disabled account and mailbox in Resource forest. 

    '  This can only be done once the objectSID from the account domain 

    '  is available. Create a metaverse Binary attribute called SID

    '  and flow objectSid -&gt; SID.

    '  The account is disabled because no password is set. Alternatively set

    '  a random password and disable using userAccountControl.

    MA = mventry.ConnectedMAs("ResourceForest")

    If MA.Connectors.Count = 0 AndAlso mventry("SID").IsPresent Then

      rdn = "CN=" &amp; mventry("displayName").StringValue

      dn = MA.EscapeDNComponent(rdn).Concat("OU=LinkedMailboxes,OU=MyOrg, " _

                                            &amp; "dc=resourcedomain,dc=local")

      mailNickname = mventry("mailNickname").StringValue

      homeMDB = "CN=" &amp; mventry("MDB").StringValue _

         &amp; ",CN=" &amp; mventry("SG").StringValue _

         &amp; ",CN=InformationStore,CN=" &amp; mventry("MailServer").StringValue _

         &amp; ",CN=Servers,CN=Exchange Administrative Group (FYDIBOHF23SPDLT)" _

         &amp; ",CN=Administrative Groups,CN=First Organization" _

         &amp; ",CN=Microsoft Exchange,CN=Services,CN=Configuration" _

         &amp; ",DC=mydomain,DC=local"  

      csentry = ExchangeUtils.CreateMailbox(MA, dn, mailNickname, homeMDB)

      csentry.DN = dn

      csentry("msExchMasterAccountSid").BinaryValue = mventry("SID").BinaryValue

      'The following setting is optional but can help with tracking the mailbox user.

       csentry("extensionAttribute1").Value = "accountdomain\" _

                                              &amp; mventry("uid").StringValue

       csentry.CommitNewConnector()

     End If

  End Select

End Sub</pre>
<p> </p>
<h3 style="color: #365f91;">Assign Resource Mailbox Permissions – Exchange 2007, powershell</h3>
<p> </p>
<p>The following script assigns the FullAccess and SendAs permissions to a resource forest mailbox.<br />
The resource forest account needs to have the domain\username of the user’s actual account written to extensionAttribute1, as per the provisioning code above.</p>
<pre>$Filter = "(&amp;(ObjectCategory=user)(extensionAttribute1=*))"

$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter)

$Searcher.Findall() | Foreach-Object -Process {

$alias = [string]$_.properties.item("mailNickname")

$user = [string]$_.properties.item("extensionAttribute1")

Add-MailboxPermission -Identity $alias -AccessRights FullAccess, SendAs -User $user

}</pre>
<p> </p>
<h3 style="color: #365f91;">Create a Contact</h3>
<p> </p>
<pre>Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision

  Dim csentry As CSEntry

  Dim MA As ConnectedMA

  Dim dn As ReferenceValue

  Dim rdn As String

  Dim mailNickname As String

  Dim mail As String

  Select Case mventry.ObjectType

  Case "person"

     MA = mventry.ConnectedMAs("MYDOMAIN")

     If MA.Connectors.Count = 0 Then

       rdn = "CN=" &amp; mventry("displayName").StringValue

       dn = MA.EscapeDNComponent(rdn).Concat("OU=Contacts,OU=MyOrg, " _

                                            &amp; "dc=mydomain,dc=local")

       mail = mventry("mail").StringValue

       'The mailNickname is only for internal Exchange purposes.

       'You could just as easily use an id number from the source data.

       mailNickname = mventry("mail").Value.Split("@")(0)

       csentry = ExchangeUtils.CreateMailEnabledContact(MA, dn, mailNickname, mail)

       csentry.DN = dn

       csentry.CommitNewConnector()

    End If

  End Select

End Sub</pre>
<p> </p>
<h3 style="color: #365f91;">Create a Distribution List</h3>
<p> </p>
<pre>Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision

  Dim csentry As CSEntry

  Dim MA As ConnectedMA

  Dim dn As ReferenceValue

  Dim rdn As String

  Dim mailNickname As String

  Dim mail As String

  Select Case mventry.ObjectType

  Case "group"

    MA = mventry.ConnectedMAs("MYDOMAIN")

    If MA.Connectors.Count = 0 Then

      rdn = "CN=" &amp; mventry("cn").StringValue

      dn = MA.EscapeDNComponent(rdn).Concat("OU=Groups,OU=MyOrg, " _

                                            &amp;"dc=mydomain,dc=local")

      mailNickname = mventry("mailNickname").StringValue

      csentry = ExchangeUtils.CreateDistributionlist(MA, dn, mailNickname)

      csentry.DN = dn

      csentry.CommitNewConnector()

    End If

  End Select

End Sub</pre>
<p> </p>
<h3 style="color: #365f91;">Create a Mail-Enabled Security Group</h3>
<p> </p>
<pre>Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision

  Dim csentry As CSEntry

  Dim MA As ConnectedMA

  Dim dn As ReferenceValue

  Dim rdn As String

  Dim mailNickname As String

  Dim mail As String

  Select Case mventry.ObjectType

  Case "group"

    MA = mventry.ConnectedMAs("MYDOMAIN")

    If MA.Connectors.Count = 0 Then

      rdn = "CN=" &amp; mventry("cn").StringValue

      dn = MA.EscapeDNComponent(rdn).Concat("OU=Groups,OU=MyOrg, " _

                                            &amp; "dc=mydomain,dc=local")

      mailNickname = mventry("mailNickname").StringValue

      csentry = MA.Connectors.StartNewConnector("group")

      csentry("groupType").Value = -2147483640  'Universal Security

      csentry("displayName").Value = mventry("cn").StringValue

      csentry("mailNickname").Value = mailNickname

      csentry.DN = dn

      csentry.CommitNewConnector()

    End If

  End Select

End Sub</pre>
<p> </p>
<h3 style="color: #365f91;">Create a Dynamic Distribution List</h3>
<p> </p>
<p>This MVExtension code snippet creates Department DDLs.<br />
The department names have been imported into department objects in the Metaverse.<br />
The users’ department attribute matches exactly the department names.</p>
<pre>Public Sub Provision(ByVal mventry As MVEntry) Implements IMVSynchronization.Provision

  Dim csentry As CSEntry

  Dim MA As ConnectedMA

  Dim dn As ReferenceValue

  Dim rdn As String

  Dim mailNickname As String

  Dim mail As String

  Select Case mventry.ObjectType

  Case "department"

    MA = mventry.ConnectedMAs("MYDOMAIN")

    If MA.Connectors.Count = 0 Then

      rdn = "CN=" &amp; mventry("cn").StringValue

      dn = MA.EscapeDNComponent(rdn).Concat("OU=DDLs,OU=MyOrg, " _

                                            &amp; "dc=mydomain,dc=local")

      mailNickname = mventry("mailNickname").StringValue

      csentry = MA.Connectors.StartNewConnector("msExchDynamicDistributionList")

      csentry.DN = dn

      csentry("displayName").Value = mventry("cn").StringValue

      csentry("mailNickname").Value = mailNickname

      'The following filter selects users whose department equals the DDL cn

      csentry("msExchDynamicDLFilter").Value = "(&amp;(!cn=SystemMailbox{*})" _

         &amp; "(&amp;(&amp;(&amp;(&amp; (mailnickname=*)" _ 

         &amp; "(| (&amp;(objectCategory=person)(objectClass=user)" _

         &amp; "(|(homeMDB=*)(msExchHomeServerName=*))) )))" _

         &amp; "(objectCategory=user)(department=" _

         &amp; mventry("cn").StringValue &amp; "))))"

      csentry("msExchDynamicDLBaseDN").Value = "OU=Groups,OU=MyOrg, " _

                                            &amp; "dc=mydomain,dc=local"

      csentry.CommitNewConnector()

    End If

  End Select

End Sub</pre>
<p> </p>
<h2 style="color: #365f91;">ILM Forum Threads</h2>
<ul>
<li><a href="http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/be387815-89ef-40cc-b063-91c9a5cb0e69/">Provisioning Exchange 2007 with ILM 2007</a></li>
<li><a href="http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/dd7f2cfe-6bd2-4d99-b933-5356a5576e15/">ILM With FP1 and Exchange 2007</a></li>
<li><a href="http://social.technet.microsoft.com/Forums/en-CA/identitylifecyclemanager/thread/8c172bab-042f-45b7-ba73-14a8a27b9f0c">Exchange 2007 &#8216;Shared&#8217; Mailbox Provisioning with ExchangeUtils</a></li>
<li><a href="http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/20485235-cd87-4550-a912-d6340bd8f8a0/">Attribute List for Exchnage 2003</a></li>
</ul>
<p> </p>
<h2 style="color: #365f91;">About the Author</h2>
<p>Carol Wapshere has been working in IT since 1990, and has since worked in many different organizations, across four different countries. She started out in Netware then moved into Microsoft server products, picking up an assortment of skills in other non-Microsoft systems along the way. She first started working with MIIS in 2005 and loved how it could be used to tie together disparate systems, bringing in much-needed order, and making lots of tedious jobs just disappear.</p>
<p>Thanks to Markus Vilcinskas and Peter Geelan for their help with this document.</p>
<hr class="sig" />
<p>http://www.wapshere.com/missmiis</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/managing-exchange-200020032007-with-ilm-2007/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Creating user home directories &#8211; Windows version</title>
		<link>http://www.wapshere.com/missmiis/creating-user-home-directories-windows-version</link>
		<comments>http://www.wapshere.com/missmiis/creating-user-home-directories-windows-version#comments</comments>
		<pubDate>Mon, 21 Sep 2009 09:31:19 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[AD]]></category>
		<category><![CDATA[ILM 2007]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=554</guid>
		<description><![CDATA[I last blogged about provisioning home directories such a long time ago that I talked about Netware. I also used a SQL table alongside to keep track of a status field as I was doing some end-of-life management &#8211; zipping up the folder and stowing it in an archive location. But we don&#8217;t need to [...]]]></description>
			<content:encoded><![CDATA[<p>I last blogged about provisioning home directories such a long time ago that I talked about <a href="http://www.wapshere.com/missmiis/creating-an-extensible-ma">Netware</a>. I also used a SQL table alongside to keep track of a status field as I was doing some end-of-life management &#8211; zipping up the folder and stowing it in an archive location.</p>
<p>But we don&#8217;t need to be that fancy. If all you want is to create a regular Windows-type home folder, at the same time as you create the AD user account, then here&#8217;s a way to do with an XMA.<br />
<span id="more-554"></span></p>
<h2>Overview</h2>
<p>To summarise the approach:</p>
<ol>
<li>Create the user account in AD,
<ul>
<li>Flow the SID back into the metaverse &#8211; proof the account exists,</li>
</ul>
</li>
<li>Create the home folder using an XMA,
<ul>
<li>Flow the path back into the metaverse,</li>
</ul>
</li>
<li>Flow the path out to the user&#8217;s homeDirectory attribute through the AD MA.</li>
</ol>
<h2>Metaverse Schema</h2>
<p>Add the following two attributes to the &#8220;person&#8221; type (or whatever you are using for your user objects):</p>
<ul>
<li>userSid (Binary)</li>
<li>homeDirectory (String)</li>
</ul>
<h2>Create the XMA</h2>
<p>Create an Extensible MA and call it &#8220;HomeFolders&#8221;. (Note: I don&#8217;t use spaces in my MA names so I have no scripting hassles.)</p>
<h3>Configure Connection Information</h3>
<p>Choose &#8220;Import and Export&#8221; and &#8220;Call based&#8221;.</p>
<p>Connected data source extension: &#8220;HomeFolders_CSExtension.dll&#8221;  (we will create the extension later)</p>
<p>Connection information: If you want to use this you will have to work out how to use the logon details in your CSExtension code to force the Import and Export steps to use this account instead of the ILM service account. I&#8217;m no great programmer, and to this day I&#8217;ve always managed to get away with giving the ILM service account the permissions I need. In this case it needs permission to create home folders and set ACLs.</p>
<h3>Configure Additional Parameters</h3>
<p>None</p>
<h3>Select Template Input File</h3>
<p>I created a text file called homefolders.txt and saved it to the MaData folder. In the file I have the following line:</p>
<p><code>path,folder,server,owner</code></p>
<p>Select the file and choose &#8220;Delimited&#8221; as the file type.</p>
<p>It is useful to keep this template file. Later, if you need to change the schema, you can modify this file and use it in the Refresh Schema operation.</p>
<h3>Delimited Text Format</h3>
<p>Tick &#8220;Use first row for header names&#8221;.</p>
<p>Select &#8220;Comma&#8221; as the delimiter.</p>
<h3>Configure Attributes</h3>
<p>Set Anchor: path</p>
<p>I also click Advanced and change the Fixed object type to &#8220;folder&#8221;.</p>
<h3>Define Object Types</h3>
<p>Leave on the default &#8211; only path is required.</p>
<h3>Connector Filter</h3>
<p>None. Use this if there are certain folder names you want to exclude.</p>
<h3>Join and Projection Rules</h3>
<p>If you&#8217;re lucky the folder names will match the sAMAccoutNames and you can join on that. You could also flow homeDirectory back from the AD MA and join on the path.</p>
<p>Try very hard to avoid manual joins in such an MA as you have no real way to flow an identifier back out to the folder itself (something you should <em>always</em> do if manual joins have been used &#8211; you should always be able to clear and re-import a connector space). If you have to go the manual join way then consider using a SQL table alongside this MA, as in <a href="http://www.wapshere.com/missmiis/creating-an-extensible-ma">this post</a>, to keep track of folder owners.</p>
<h3>Attribute Flow</h3>
<p>One inbound flow only:</p>
<p><code>path --&gt; homeDirectory</code></p>
<h3>Configure Deprovisioning</h3>
<p>&#8220;Make them disconnectors&#8221;</p>
<p>You can delete home folders with an XMA &#8211; you just need to add the appropriate code in your CSExtension to deal with modificationType = delete. But for this example I&#8217;m just creating the folder.</p>
<h3>Configure Extensions</h3>
<p>Click <strong>Finish</strong> to create the MA.</p>
<h2>Write the CSExtension</h2>
<p>With the new MA selected, click on <strong>Create Extension Project</strong>. In Project type choose &#8220;Connected Data Source Extension&#8221; and then enter as the name &#8220;HomeFolders_CSExtension&#8221;. Make sure your Project Location is correct and then click <strong>OK</strong>.</p>
<h3>GenerateImportFile</h3>
<p>The first step is to write the Import step. This should go out and find all the existing home folders and then populate a csv file along the lines of the template you created above. The actual import into the connector space then occurs from the csv file.</p>
<p>Note that my GetOwner function is a fudge &#8211; I actually just return the first domain account that I find with rights to the folder. This is acceptable to me because <em>I am not going to use this MA to modify existing rights</em>. All I really care about is checking that the user was correctly assigned to a newly created home folder. Typically the user will be the only domain account with explicit (as opposed to inherited) rights to the folder, so this should work fine.</p>
<pre>  Dim Servers As String() = {"server1", "server2", "server3"}

    Public Sub GenerateImportFile(ByVal filename As String, ByVal connectTo As String, ByVal user As String, ByVal password As String, ByVal configParameters As ConfigParameterCollection, ByVal fullImport As Boolean, ByVal types As TypeDescriptionCollection, ByRef customData As String) Implements IMAExtensibleFileImport.GenerateImportFile
        Dim server As String
        Dim path As String
        Dim folders As System.Collections.IEnumerator
        Dim fw As StreamWriter

        Try
            'Open the output file specified in the run profile
            fw = New System.IO.StreamWriter(filename, False)
            fw.WriteLine("path,folder,server,owner")
        Catch ex As Exception
            Throw New UnauthorizedAccessException("Unable to open file: " &amp; filename)
        End Try

        For Each server In Servers
            folders = Directory.GetDirectories("\\" &amp; server &amp; "\homedir").GetEnumerator
            While folders.MoveNext
                path = folders.Current.ToString
                'For each folder, write an entry into the import file
                fw.WriteLine(path &amp; "," _
                            &amp; path.Split("\".ToCharArray)(4) &amp; "," _
                            &amp; path.Split("\".ToCharArray)(2) &amp; "," _
                            &amp; GetOwner(path))
            End While
        Next
        fw.Close()
    End Sub

   Function GetOwner(ByVal path As String) As String
        ' This function does not get the actual owner of the folder,
        ' rather it returns the first domain trustee account.
        ' This is not very precise but the purpose is just to check the ACL
        ' for new folders so it will do.
        Dim AccessRules As AuthorizationRuleCollection
        Dim AuthRule As AuthorizationRule = Nothing
        Dim Owner As String = ""
        Dim Fs As FileSecurity

        Try
            Fs = New FileSecurity(path, AccessControlSections.Access)
            AccessRules = Fs.GetAccessRules(True, False, Type.GetType("System.Security.Principal.NTAccount"))
            If Not AccessRules Is Nothing Then
                For Each AuthRule In AccessRules
                    If AuthRule.IdentityReference.ToString.IndexOf("MYDOMAIN\") = 0 Then
                        Owner = AuthRule.IdentityReference.ToString
                        Exit For
                    End If
                Next
            End If
        Catch ex As Exception
        End Try

        If AuthRule Is Nothing Or _
           Owner = "" Or _
           Owner.IndexOf("MYDOMAIN\") &lt; 0 Then
            Return ""
        Else
            Return Owner
        End If

    End Function</pre>
<p>Once this code is in place you should be able to run an Import step on your MA. You will need to specify an import file name &#8211; just something like &#8220;import.csv&#8221; is fine. Once your code is working you will be able to see the file created in the MaData\HomeFolders directory, and populated with information about the folders it has found.</p>
<h3>ExportEntry</h3>
<p>I&#8217;m going to include the code for the ExportEntry sub here, but we won&#8217;t have anything to export until we modify our MVExtension provisioning code to create new folder objects. More on that below.</p>
<pre>    Public Sub ExportEntry(ByVal modificationType As ModificationType, ByVal changedAttributes As String(), ByVal csentry As CSEntry) Implements IMAExtensibleCallExport.ExportEntry

        If modificationType = Microsoft.MetadirectoryServices.ModificationType.Add Then
            Directory.CreateDirectory(csentry("path").StringValue)

            If Directory.Exists(csentry("path").StringValue) Then
                Try
                    ' Get current ACL from the folder
                    Dim security As DirectorySecurity = Directory.GetAccessControl(csentry("path").StringValue)

                    ' Create a new rule granting owner full access
                    Dim rule As New FileSystemAccessRule(New NTAccount(csentry("owner").StringValue.ToLower), FileSystemRights.FullControl, AccessControlType.Allow)

                    ' Add the rule to the existing rules
                    security.AddAccessRule(rule)

                    ' Persist the changes
                    Directory.SetAccessControl(csentry("path").StringValue, security)

                Catch ex As Exception
                    ' If the ACL failed the delete the folder so the operation can be retried later.
                    Directory.Delete(csentry("path").StringValue)
                    Throw New EntryExportException("Failed to set ACL on " &amp; csentry("path").StringValue _
                              &amp; " - deleting folder. " &amp; vbCrLf &amp; ex.Message)
                End Try

            End If
        End If

    End Sub</pre>
<h2>Provisioning</h2>
<p>Now we want to create the home folder.</p>
<p>As I indicated at the top of this post, you need some kind of proof that the user account was created before you create the home folder. The reason is permissions &#8211; you can&#8217;t grant permissions to a user that doesn&#8217;t exist yet. Technically you could provision your user and home folder objects at the same time, so long as you made sure the AD MA was exported first, but fundamentally I don&#8217;t like this. Even though it means running a series of export/import-syncs in a row, I much prefer to wait for the user account before I provision the home folder.</p>
<p>And the way I do this is by checking the Sid. I flow the userSid back into the Metaverse from the AD MA and use this as a test in my provisioning logic for home folders.</p>
<pre>  Sub HomeFolder_Provisioning(ByVal mventry As MVEntry)
        Dim HFMA As ConnectedMA = mventry.ConnectedMAs("HomeFolders")
        Dim path As String = ""
        Dim server As String = ""

        '' Generate the path based on location
        If mventry("location").IsPresent Then
            Select Case mventry("location").StringValue.ToLower
                Case "london"
                    server = "server1"
                Case "zürich"
                    location = "server 2"
                Case Else
                    server = "server3"
            End Select
            path = "\\" &amp; server &amp; "\homedir\" &amp; mventry("uid").StringValue
        End If

        '' Should the folder exist? AD account must be provisioned first.
        '' Note: Folders are NOT deleted. ShouldExist = FALSE just prevents creation.
        If mventry("Sid_AD").IsPresent Then
            ShouldExist = True
        Else
            ShouldExist = False
        End If

        '' Check if the folder already exists
        Select Case HFMA.Connectors.Count
            Case 0
                DoesExist = False
            Case 1
                DoesExist = True
            Case Else
                Throw New UnexpectedDataException("The metaverse person object with employeeID =" _
                 &amp; mventry("employeeID").StringValue &amp; " has more than one connector in MA " &amp; HFMA.Name)
        End Select

        '' Take action based on values of ShouldExist and DoesExist
        If ShouldExist And DoesExist Then
            'Do nothing

        ElseIf ShouldExist And Not DoesExist Then
            'Create folder object in CS
            Dim csentry As CSEntry = HFMA.Connectors.StartNewConnector("folder")
            csentry("path").Value = path
            csentry("server").Value = server
            csentry("folder").Value = mventry("uid").StringValue
            csentry("owner").Value = "MYDOMAIN\" &amp; mventry("uid").StringValue
            csentry.CommitNewConnector()

        ElseIf Not ShouldExist And DoesExist Then
            'Do nothing

        Else 'Should not exist and does not exist
            'Do nothing
        End If

    End Sub</pre>
<p>All going well, you should now be able to Sync your AD MA, and any AD accounts that are missing a home folder should have one provisioned.</p>
<p>Start testing your exports by restricting the Export step to only process one object at a time.</p>
<h2>Update User homeDirectory</h2>
<p>The final step is to flow the path out to the homeDirectory attribute on the user, using the AD MA.</p>
<p><code>homeDirectory --&gt; homeDirectory</code></p>
<p>If you want to map a particulay drive letter at the same time then flow a constant to homeDrive, eg.,</p>
<p><code>"H:" --&gt; homeDrive</code></p>
<h2>Doing more</h2>
<p>You can do more. As I mentioned above you could modify your ExportEntry sub to process a delete, and then delete the home folder &#8211; though you would want to tread very carefully. There are bound to be far more interesting things you can do with the permissions, or with sharing the folder &#8211; but I&#8217;ll leave that for you to work out. Unless I have to do something like that myself &#8211; and then I will no doubt write it up here.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/creating-user-home-directories-windows-version/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ExternalEmailAddress is mandatory on MailUser</title>
		<link>http://www.wapshere.com/missmiis/externalemailaddress-is-mandatory-on-mailuser</link>
		<comments>http://www.wapshere.com/missmiis/externalemailaddress-is-mandatory-on-mailuser#comments</comments>
		<pubDate>Tue, 15 Sep 2009 09:07:07 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[Exchange 2007]]></category>
		<category><![CDATA[ILM 2007]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=549</guid>
		<description><![CDATA[I just got this error while attempting to provision Exchange 2007 users with ILM 2007 FP1: Event Type:    Error Event Source:    MIIServer Event Category:    Server Event ID:    6801 Date:        15.09.2009 Time:        10:14:02 User:        N/A Computer:    ILMSERVER Description: The extensible extension returned an unsupported error in MIIS. The stack trace is: "Microsoft.MetadirectoryServices.ExtensionException: **** [...]]]></description>
			<content:encoded><![CDATA[<p>I just got this error while attempting to provision Exchange 2007 users with ILM 2007 FP1:</p>
<p><code> </code></p>
<pre>Event Type:    Error
Event Source:    MIIServer
Event Category:    Server
Event ID:    6801
Date:        15.09.2009
Time:        10:14:02
User:        N/A
Computer:    ILMSERVER
Description:
The extensible extension returned an unsupported error in MIIS.
The stack trace is:

"Microsoft.MetadirectoryServices.ExtensionException:
**** ERROR ****

ExternalEmailAddress is mandatory on MailUser.

**** END ERROR ****

**** ERROR ****

The mail contact and mail user must have a valid external e-mail address.

**** END ERROR ****

at Exch2007Extension.Exch2007ExtensionClass.AfterExportEntryToCd(Byte[] origAnchor, String origDN, String origDeltaEntryXml, Byte[] newAnchor, String newDN, String failedDeltaEntryXml, String errorMessage)
Microsoft Identity Integration Server 3.3.0118.0"</pre>
<p>This event had been asked about on the Technet forum, but the answers talked about rollup versions &#8211; and I had RU9 on both the Exchange and ILM servers.</p>
<p>Eventually I figured out there was a typo in my homeMDB string. The clue was that all the expected mail attributes were populated in AD, except homeMDB.</p>
<p>Now what &#8220;ExternalEmailAddress&#8221; has to do with homeDMB I do not know!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/externalemailaddress-is-mandatory-on-mailuser/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Adding Exchange 2007 mailboxes to existing users</title>
		<link>http://www.wapshere.com/missmiis/adding-exchange-2007-mailboxes-to-existing-users-part-2</link>
		<comments>http://www.wapshere.com/missmiis/adding-exchange-2007-mailboxes-to-existing-users-part-2#comments</comments>
		<pubDate>Sun, 05 Apr 2009 07:03:44 +0000</pubDate>
		<dc:creator>Carol</dc:creator>
				<category><![CDATA[Exchange 2007]]></category>
		<category><![CDATA[ILM 2007]]></category>

		<guid isPermaLink="false">http://www.wapshere.com/missmiis/?p=403</guid>
		<description><![CDATA[A while back I proposed  a powershell XMA approach to the problem of adding Exchange 2007 mailboxes to existing AD users. This was because my old method of adding an Exchange 2003 mailbox by populating a couple of extra attributes did not seem to work with Exchange 2007. However, in  a recent thread on the [...]]]></description>
			<content:encoded><![CDATA[<p>A while back I proposed  a <a href="http://www.wapshere.com/missmiis/?p=236">powershell XMA approach</a> to the problem of adding Exchange 2007 mailboxes to existing AD users. This was because my old method of <a href="http://www.wapshere.com/missmiis/?p=19">adding an Exchange 2003 mailbox</a> by populating a couple of extra attributes did not seem to work with Exchange 2007.</p>
<p>However, in  a <a href="http://social.technet.microsoft.com/Forums/en-US/identitylifecyclemanager/thread/4b230cd3-1a74-49c8-ac68-92fe1446e890">recent thread</a> on the Technet forum, Michael D&#8217;Angelo listed all the attributes that he has found are needed for an Exchange 2007 mailbox. I eventually managed to test this myself in a lab and, surprisingly, it now seems to be working perfectly &#8211; and in fact I only needed to populate the same attributes as for Exchange 2003. These are:</p>
<p>displayName<br />
mailNickname<br />
homeMDB<br />
mDBUseDefaults</p>
<p>I was using Exchange 2007 rollup 9 in the lab. Not sure if anything has changed with the rollups to make it work now.</p>
<p>Note: this post was modified on the 24/7/09 as I prefer the posts to represent what I think is correct now instead of what I thought was correct at the time.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.wapshere.com/missmiis/adding-exchange-2007-mailboxes-to-existing-users-part-2/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
