Wednesday, July 6, 2011

ILM Sample Code PrepareMoveRequest Exchange 2010 Cross Forest Mailbox Moves - Multi Forest

Microsoft Identity Lifecycle Manager Service Pack 1 Feature Pack 1 (ILM 2007 SP1 FP1) can be used to pre-stage the user accounts with the appropriate attributes in a destination forest for cross-forest mailbox moves The out of the box GALSync MA cannot be used since it creates contact object instead of user object required for Online Mailbox Move. Microsoft has provided a sample code extension for the management agents to perform this which can be downloaded from:

http://www.microsoft.com/download/en/details.aspx?id=17741

The ILM sample code demonstrates how to sync source mailbox as Mail Enabled Users (MEU).

The problem with this sample code is was only designed for migration between two forests. My customer wishes to pre-stage user accounts from 2 forests into a new forest meaning I have two source forests! In the OneWaySync.xml file by default we have:

<?xml version="1.0" encoding="utf-8" ?>
<config>
<TargetOU>ou=MaiLboxmoves,DC=targetdom,DC=exchange,DC=contoso,DC=com</TargetOU>
<SourceMAName>Source Forest</SourceMAName>
<TargetMAName>Target Forest</TargetMAName>
</config>


I worked with a Microsoft FIM (Forefront Identity Manager) expert named Tracy Yu and together we made changes to the sample code and recompiled a new DLL to account for multiple source forests.

The file we needed to edit was Microsoft.Exchange.Sample.OneWayGALSync.MVRules.dll. The source code for this file is located under the solution folder under the sample ILM sample code package in a file named Microsoft.Exchange.Sample.OneWayGALSync.MVRules.cs. Here is our new code - in red are any changes made:

// ---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//

// ---------------------------------------------------------------------------

///
/// Example ILM 2007 Provisioning Rule to perform one-way cross-forest GAL synchronization
/// with required prerequisites for mailbox moves from source to target forests
///


using System;
using System.Xml;
using Microsoft.MetadirectoryServices;

namespace Microsoft.Exchange.Sample.OneWayGALSync.MVRules
{
public class MVExtensionObject : IMVSynchronization
{
// define variables for configuration setting
private string targetOU;
private string sourceMAName;
//add by tracy
private string sourceMAName1;
private string targetMAName;

public MVExtensionObject()
{
// No additional constructor logic required
}

void IMVSynchronization.Initialize()
{
// initialize the provisioning rules configuration parameters
// from config file in ILM Extensions directory
XmlDocument xmlConfigFile = new XmlDocument();
xmlConfigFile.Load(Utils.ExtensionsDirectory + "\\OneWaySync.xml");
XmlNode xmlConfig = xmlConfigFile.SelectSingleNode("config");
targetOU = xmlConfig.SelectSingleNode("TargetOU").InnerText.Trim();
sourceMAName = xmlConfig.SelectSingleNode("SourceMAName").InnerText.Trim();
targetMAName = xmlConfig.SelectSingleNode("TargetMAName").InnerText.Trim();
//add by tracy
sourceMAName1 = xmlConfig.SelectSingleNode("SourceMAName1").InnerText.Trim();
}

void IMVSynchronization.Terminate()
{
// No additional termination logic required
}

// For each Mailbox in the Source Forest Provision a connected
// Mail User object in the Target Forest.
void IMVSynchronization.Provision(MVEntry mventry)
{
// ConnectedMA sourceMA = mventry.ConnectedMAs[sourceMAName];
//ConnectedMA targetMA = mventry.ConnectedMAs[targetMAName];
//CSEntry csentry;

//modify by tracy

ConnectedMA targetMA = mventry.ConnectedMAs[targetMAName];
CSEntry csentry;
ConnectedMA sourceMA = null;
ConnectedMACollection MACols = mventry.ConnectedMAs;

foreach(ConnectedMA tmpMA in MACols)
{
if(tmpMA.Name == sourceMAName1)
{
sourceMA = mventry.ConnectedMAs[sourceMAName1];
}
else if (tmpMA.Name == targetMAName)
{
//did nothing
}
else
{
sourceMA = mventry.ConnectedMAs[sourceMAName];
}
}


// if the object has been deleted from Source Forest then delete it
// from Target Forest
if (sourceMA.Connectors.Count == 0)
{
targetMA.Connectors.DeprovisionAll();
return;
}

// This example provisioning rule excludes certain Exchange object types
if (sourceMA.Connectors.Count != 1 ||
!mventry["msExchHomeServerName"].IsPresent ||
!mventry["mailNickName"].IsPresent)
{
return;
}

ReferenceValue targetDN = targetMA.EscapeDNComponent("CN=" + mventry["cn"].Value).Concat(targetOU);

// check for Contacts in target forest that have to be converted to MEUs
for (int index = 0; index < targetMA.Connectors.Count; index++)
{
if (targetMA.Connectors.ByIndex[index].ObjectType.ToLower().Equals("contact"))
{
bool duplicateDN = targetMA.Connectors.ByIndex[index].DN.ToString().ToLower().Equals(targetDN.ToString().ToLower());

targetMA.Connectors.ByIndex[index].Deprovision();
if (duplicateDN)
return;
}
}

if (targetMA.Connectors.Count != 0)
return;

// provision a new AD User Object in the targetOU container
csentry = targetMA.Connectors.StartNewConnector("user");
csentry.DN = targetDN;

// provision the following minimal attributes on the new MailUser object
csentry["samAccountName"].Value = mventry["samAccountName"].Value;
csentry["msexchRecipientTypeDetails"].IntegerValue = 0x80;// MailUser
csentry["userAccountControl"].IntegerValue = 0x202; // ACCOUNTDISABLE | NORMAL_ACCOUNT
csentry["msexchRecipientDisplayType"].IntegerValue = -1073741818; // equivalent to *unsigned* 0xC0000006 i.e. ACL-able, Synced, MailUser
csentry["msExchMasterAccountSID"].Value = mventry["msExchMasterAccountSID"].IsPresent ? mventry["msExchMasterAccountSID"].Value : mventry["objectSID"].Value;
csentry["msExchMailboxGUID"].Value = mventry["msExchMailboxGUID"].Value;
csentry["mailNickname"].Value = mventry["mailNickname"].Value;
csentry["proxyAddresses"].Values = mventry["proxyAddresses"].Values;
csentry["proxyAddresses"].Values.Add("X500:" + mventry["legacyExchangeDN"].Value); // this ensures migrated mail that addresses this user is reply-able in target forest
csentry["msExchVersion"].IntegerValue = 44220983382016; // Set version to E14

csentry.CommitNewConnector();
}

bool IMVSynchronization.ShouldDeleteFromMV(CSEntry csentry, MVEntry mventry)
{
throw new EntryPointNotImplementedException();
}
}
}


Now when we can create two source domains in our OneWaySync.xml file for two source management agents.

<?xml version="1.0" encoding="utf-8" ?>
<config>
<TargetOU>ou=MaiLboxmoves,DC=targetdom,DC=exchange,DC=contoso,DC=com</TargetOU>
<SourceMAName>Source Forest 1</SourceMAName>
<SourceMAName1>Source Forest 2</SourceMAName1>
<TargetMAName>Target Forest</TargetMAName>
</config>


Thanks Tracy!

1 comment:

  1. Hi Clint,

    Any idea how this can be done with multiple source forests? I have about 7 of them but I'm stuck on the last part in red.

    Thanks,
    Peter

    ReplyDelete