I have FIM 2010 R2 with a FIM MA and a ECMA connector to Dynamics AX which is not exporting or creating any useful errors. Imports work as expected from the connector, but exports simply fail seemingly before calling PutExportEntriesResults with no error messages other than "stopped server." I found one other post utilizing a ECMA connector that had similar export issues and a reinstallation of FIM fixed it (http://social.technet.microsoft.com/Forums/en-US/df3cb058-dde6-428b-b9e2-41570e8b4b44/fim-ecma-20-export-fails-with-stoppedserver), so I rebuilt the environment on Windows 2008 R2 instead of Server 2012, but continue to get the stopped-server errors.
Here is the code for the extension:
using System; using System.IO; using System.Xml; using System.Text; using System.Collections.Specialized; using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.MetadirectoryServices; using Microsoft.Dynamics.BusinessConnectorNet; using System.Security.Principal; using System.Data; using NLog; namespace FimSync_Ezma { public class EzmaExtension : IMAExtensible2CallExport, IMAExtensible2CallImport, //IMAExtensible2FileImport, //IMAExtensible2FileExport, //IMAExtensible2GetHierarchy, IMAExtensible2GetSchema, IMAExtensible2GetCapabilities, IMAExtensible2GetParameters //IMAExtensible2GetPartitions { private int m_importDefaultPageSize = 12; private int m_importMaxPageSize = 1000; private int m_exportDefaultPageSize = 10; private int m_exportMaxPageSize = 20; public string myUser; public string myBcPassword; public string myBcUser; public string myDomain; public string myCompany; public string myServer; public string myLanguage; DataSet da; public string myFirst; public string myLast; public string myEmail; //public string myEmpID; public string myFull; public string myNetworkDomain; public string mySamAccountName; public object myObjectSid; public string myUserAccountControl; public string myCo; public string myConfigPath; Microsoft.Dynamics.BusinessConnectorNet.Axapta DynAx; Microsoft.Dynamics.BusinessConnectorNet.AxaptaRecord DynRec; NLog.Logger log; // // Constructor // public EzmaExtension() { log = LogManager.GetLogger("FIM-DynamicsSync"); log.Trace("Extension Started"); DynAx = new Axapta(); } public MACapabilities Capabilities { get { MACapabilities myCapabilities = new MACapabilities(); myCapabilities.ConcurrentOperation = true; myCapabilities.ObjectRename = false; myCapabilities.DeleteAddAsReplace = true; myCapabilities.DeltaImport = false; myCapabilities.DistinguishedNameStyle = MADistinguishedNameStyle.None; myCapabilities.ExportType = MAExportType.AttributeUpdate; myCapabilities.NoReferenceValuesInFirstExport = false; myCapabilities.Normalizations = MANormalizations.None; return myCapabilities; } } /// <summary> /// This Function describes the configuration parameters that will be /// required by the management agent to successfully make a connection to the call based system, /// for example a webservice. /// </summary> /// <param name="configParameters"></param> /// <param name="page"></param> /// <returns></returns> public IList<ConfigParameterDefinition> GetConfigParameters(KeyedCollection<string, ConfigParameter> configParameters, ConfigParameterPage page) { log.Trace("Getting Config Parameters"); List<ConfigParameterDefinition> configParametersDefinitions = new List<ConfigParameterDefinition>(); switch (page) { case ConfigParameterPage.Connectivity: configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("User Name", "")); configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("bcUser", "")); configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("bcPassword", "")); configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("Domain", "")); configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("Company", "")); configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("Server", "")); configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("Language", "")); configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter("ConfigPath", "")); break; case ConfigParameterPage.Global: break; case ConfigParameterPage.Partition: break; case ConfigParameterPage.RunStep: break; } return configParametersDefinitions; } public ParameterValidationResult ValidateConfigParameters(KeyedCollection<string, ConfigParameter> configParameters, ConfigParameterPage page) { ParameterValidationResult myResults = new ParameterValidationResult(); return myResults; } /// <summary> /// This function describes the Schema pertaining to an object in the external /// system. It contains attributes for a relevantobject in the external system, /// for example fields/columns of a table. /// </summary> /// <param name="configParameters"></param> /// <returns></returns> public Schema GetSchema(KeyedCollection<string, ConfigParameter> configParameters) { log.Trace("Getting Schema"); Microsoft.MetadirectoryServices.SchemaType personType = Microsoft.MetadirectoryServices.SchemaType.Create("Person", false); myUser = configParameters["User Name"].Value; myBcPassword = configParameters["bcPassword"].Value; myBcUser = configParameters["bcUser"].Value; myDomain = configParameters["Domain"].Value; myCompany = configParameters["Company"].Value; myServer = configParameters["Server"].Value; myLanguage = configParameters["Language"].Value; myConfigPath = configParameters["ConfigPath"].Value; var myData = this.DynAXSchema(myUser, myBcPassword, myDomain, myServer, myCompany, myLanguage); foreach (var item in myData) { log.Trace("Item: {0}", item.ToString()); if ("networkalias" == item.ToLower()) { log.Trace("anchor attribute"); personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(item, AttributeType.String)); } else if ("objectsid" == item.ToLower()) { personType.Attributes.Add(SchemaAttribute.CreateSingleValuedAttribute(item, AttributeType.Binary)); } else { personType.Attributes.Add(SchemaAttribute.CreateSingleValuedAttribute(item, AttributeType.String)); } } Schema schema = Schema.Create(); schema.Types.Add(personType); log.Trace("Schema Created"); return schema; } /// <summary> /// Creates a custom schema /// </summary> /// <param name="username"></param> /// <param name="password"></param> /// <param name="domain"></param> /// <param name="server"></param> /// <param name="company"></param> /// <param name="language"></param> /// <returns></returns> /// <remarks> /// <para>Created the custom schema from the documentation - http://msdn.microsoft.com/en-us/library/aa596081(v=ax.50).aspx </para> /// </remarks> public List<string> DynAXSchema(string username, string password, string domain, string server, string company, string language) { //System.Net.NetworkCredential nc = new System.Net.NetworkCredential(username, password); //DynAx.LogonAs(username, domain, nc, company, language, server, null); log.Trace("Setting up Schema"); var results = new List<string>(); results.Add("id"); results.Add("name"); //results.Add("email"); //results.Add("enable"); results.Add("company"); results.Add("objectSid"); results.Add("networkDomain"); results.Add("networkAlias"); //results.Add("externalUser"); return results; } /// <summary> /// This function is used to implement logic for connecting to the /// external system when an import run is executed in FIM 2010 Synchronization Service. /// </summary> /// <param name="configParameters"></param> /// <param name="types"></param> /// <param name="importRunStep"></param> /// <returns></returns> public OpenImportConnectionResults OpenImportConnection( KeyedCollection<string, ConfigParameter> configParameters, Schema types, OpenImportConnectionRunStep importRunStep) { log.Trace("Open Import Connection"); myUser = configParameters["User Name"].Value; myBcPassword = configParameters["bcPassword"].Value; myBcUser = configParameters["bcUser"].Value; myDomain = configParameters["Domain"].Value; myCompany = configParameters["Company"].Value; myServer = configParameters["Server"].Value; myLanguage = configParameters["Language"].Value; myConfigPath = configParameters["ConfigPath"].Value; log.Trace("read config values"); log.Trace("Config Params - User {0} | Password Length {1} | Domain {2} | Company {3} | Server {4} | Language {5}", myUser, myBcPassword.Length.ToString(), myDomain, myCompany, myServer, myLanguage); System.Net.NetworkCredential nc = new System.Net.NetworkCredential(myBcUser, myBcPassword, myDomain); log.Trace("setup Network Credentials using the User:{0}", nc.UserName); DynAx.LogonAs(myUser, myDomain, nc, myCompany, string.IsNullOrEmpty(myLanguage) ? null : myLanguage, string.IsNullOrEmpty(myServer) ? null : myServer, string.IsNullOrEmpty(myConfigPath) ? null : myConfigPath); log.Trace("Open Import Connection Called"); return new OpenImportConnectionResults(); } /// <summary> /// This function is executed when an import run is executed on FIM 2010 /// Synchronization Service. In this function you should program the logic to import Adds, /// Deletes and Updates to the data into FIM 2010 Connector Space etc. /// </summary> /// <param name="importRunStep"></param> /// <returns></returns> public GetImportEntriesResults GetImportEntries(GetImportEntriesRunStep importRunStep) { GetImportEntriesResults importReturnInfo = new GetImportEntriesResults(); List<CSEntryChange> csentries = new List<CSEntryChange>(); try { using (AxaptaRecord users = DynAx.CreateAxaptaRecord("UserInfo")) { users.ExecuteStmt("select * from %1"); while (users.Found) { CSEntryChange csentry1 = CSEntryChange.Create(); csentry1.ObjectModificationType = ObjectModificationType.Add; csentry1.ObjectType = "Person"; var userName = users.get_Field("name"); //string[] sep = { " " }; //var part = userName.ToString().Split(sep, StringSplitOptions.None); var stringSid = users.get_Field("sid").ToString(); byte[] binarySid = new byte[] { 00 }; log.Trace("Mapping Dynamics Data to FIM for {0}, objectSid {1}", userName, stringSid); if (stringSid != "") { var dynamicsSid = new SecurityIdentifier(stringSid); binarySid = new byte[dynamicsSid.BinaryLength]; dynamicsSid.GetBinaryForm(binarySid, 0); } csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd("name", userName)); csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd("company", users.get_Field("company"))); csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd("networkAlias", users.get_Field("networkAlias"))); csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd("networkDomain", users.get_Field("networkDomain"))); csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd("id", users.get_Field("id"))); csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd("objectSid", binarySid)); //log.Trace("Mapping Dynamics Data to FIM for {0}", userName); csentries.Add(csentry1); users.Next(); } } } catch (Exception ex) { log.Trace("Error encountered: {0}", ex.Message); } //importReturnInfo = new GetImportEntriesResults(); importReturnInfo.MoreToImport = false; importReturnInfo.CSEntries = csentries; return importReturnInfo; } /// <summary> /// This function is executed when animport run has been executed /// completely with or without errors. In this function you should program the logic for closing /// the connection anythingthat is required before or after closing. /// </summary> /// <param name="importRunStepInfo"></param> /// <returns></returns> public CloseImportConnectionResults CloseImportConnection(CloseImportConnectionRunStep importRunStepInfo) { log.Trace("Closing Import Connection"); DynAx.Logoff(); return new CloseImportConnectionResults(); } public int ImportMaxPageSize { get { return m_importMaxPageSize; } } public int ImportDefaultPageSize { get { return m_importDefaultPageSize; } } /// <summary> /// This function is used to implement logic for connecting to the /// external system when an export run is executed in FIM 2010 Synchronization Service. /// </summary> /// <param name="configParameters"></param> /// <param name="types"></param> /// <param name="exportRunStep"></param> public void OpenExportConnection(KeyedCollection<string, ConfigParameter> configParameters, Schema types, OpenExportConnectionRunStep exportRunStep) { log.Trace("Open Export Connection"); myUser = configParameters["User Name"].Value; myBcPassword = configParameters["bcPassword"].Value; myBcUser = configParameters["bcUser"].Value; myDomain = configParameters["Domain"].Value; myCompany = configParameters["Company"].Value; myServer = configParameters["Server"].Value; myLanguage = configParameters["Language"].Value; myConfigPath = configParameters["ConfigPath"].Value; System.Net.NetworkCredential nc = new System.Net.NetworkCredential(myBcUser, myBcPassword, myDomain); log.Trace("setup Network Credentials using the User:{0}", nc.UserName); try { DynAx.LogonAs(myUser, myDomain, nc, myCompany, string.IsNullOrEmpty(myLanguage) ? null : myLanguage, string.IsNullOrEmpty(myServer) ? null : myServer, string.IsNullOrEmpty(myConfigPath) ? null : myConfigPath); } catch (Exception ex) { log.ErrorException("Exception on export connection:", ex); log.Trace("Session ID: {0}", DynAx.Session().ToString()); } } /// <summary> /// This function is executed when an export run is executed on FIM 2010 /// Synchronization Service. In this function you should program the logic to export Adds, /// Deletes and Updates to the data from the FIM 2010 Connector Space to the External System. /// </summary> /// <param name="csentries"></param> /// <returns></returns> public PutExportEntriesResults PutExportEntries(IList<CSEntryChange> csentries) { log.Debug("Entering method PutExportEntries"); log.Debug("Count of csentries {0}", csentries.Count.ToString()); PutExportEntriesResults exportEntriesResults = new PutExportEntriesResults(); log.Trace("In PutExportEntriesResults"); foreach (CSEntryChange csentryChange in csentries) { List<AttributeChange> attributeChanges = new List<AttributeChange>(); //csentryChange.ErrorCodeExport = MAExportError.Success; //Default State if (csentryChange.ObjectType == "Person") { try { log.Trace("Currently performing modification type {0}", csentryChange.ObjectModificationType.ToString()); switch (csentryChange.ObjectModificationType) { case ObjectModificationType.Add: //User Creation Code var myId = GenerateId(mySamAccountName.Substring(0, 4)); createUser(csentryChange, myId); attributeChanges.Add(AttributeChange.CreateAttributeUpdate("id", myId)); exportEntriesResults.CSEntryChangeResults.Add(CSEntryChangeResult.Create(csentryChange.Identifier, attributeChanges, MAExportError.Success)); break; case ObjectModificationType.Delete: //User Deletion Code break; case ObjectModificationType.Update: case ObjectModificationType.Replace: //User Update Code in our case does both the update and replace functions in the process break; default: break; } } catch (Exception ex) { //csentryChange.ErrorCodeExport = MAExportError.ExportErrorConnectedDirectoryError;//if any error occur it will throw a connected directory export error log.ErrorException("Failure with adding a user", ex); } } exportEntriesResults.CSEntryChangeResults.Add(CSEntryChangeResult.Create(csentryChange.Identifier, attributeChanges, MAExportError.Success)); } log.Trace("Putting export entry results"); return exportEntriesResults; } /// <summary> /// This function is executed when an export run has been executed /// completely with or without errors. In this function you should program the logic for closing /// the connection anythingthat is required before or after closing. /// </summary> /// <param name="exportRunStep"></param> public void CloseExportConnection(CloseExportConnectionRunStep exportRunStep) { DynAx.Logoff(); } public int ExportDefaultPageSize { get { return m_exportDefaultPageSize; } set { m_exportDefaultPageSize = value; } } public int ExportMaxPageSize { get { return m_exportMaxPageSize; } set { m_exportMaxPageSize = value; } } /// <summary> /// </summary> /// <param name="userAccountControl"></param> /// <returns></returns> private bool DetermineIfAccountIsEnabled(string userAccountControl) { int flags = 0; int.TryParse(userAccountControl, out flags); if (!Convert.ToBoolean(flags & 0x0002)) { return true; } else { return false; } } /// <summary> /// </summary> /// <param name="baseId"></param> /// <returns></returns> private void createUser(CSEntryChange csentryChange, string myId) { // Data Consistency checking for required Attributes foreach (string attrib in csentryChange.ChangedAttributeNames) { log.Trace("attrib {0}", attrib.ToString()); switch (attrib) { //case "firstName": // myFirst = csentryChange.AttributeChanges["firstName"].ValueChanges[0].Value.ToString(); // break; //case "lastName": // myLast = csentryChange.AttributeChanges["lastName"].ValueChanges[0].Value.ToString(); // break; //case "email": // myEmail = csentryChange.AttributeChanges["email"].ValueChanges[0].Value.ToString(); // break; //case "employeeID": // myEmpID = csentryChange.AttributeChanges["employeeID"].ValueChanges[0].Value.ToString(); // break; //case "samAccountName": // mySamAccountName = csentryChange.AttributeChanges["sAMAccountName"].ValueChanges[0].Value.ToString(); // break; case "networkDomain": myNetworkDomain = csentryChange.AttributeChanges["networkDomain"].ValueChanges[0].Value.ToString(); break; case "objectSid": myObjectSid = TransformSid(csentryChange.AttributeChanges["objectSid"].ValueChanges[0].Value.ToString()); break; case "name": myFull = csentryChange.AttributeChanges["name"].ValueChanges[0].Value.ToString(); break; //case "userAccountControl": // myUserAccountControl = csentryChange.AttributeChanges["userAccountControl"].ValueChanges[0].Value.ToString(); // break; case "company": myCo = csentryChange.AttributeChanges["company"].ValueChanges[0].Value.ToString(); break; default: log.Trace("Skipping Attribute {0}", attrib.ToString()); break; } } // if (csentryChange.AttributeChanges["DisplayName"].ValueChanges[0].Value == null) // { // csentryChange.ErrorCodeExport = MAExportError.ExportErrorMissingAnchorComponent; // throw new Exception("The CSEntry Attribute Value for DisplayName does not exist, please ensure that the value is present in the connector space"); // } // if (csentryChange.AttributeChanges["uid"].ValueChanges[0].Value == null) // { // csentryChange.ErrorCodeExport = MAExportError.ExportErrorMissingAnchorComponent; // throw new Exception("The CSEntry Attribute Value for UsageLocation does not exist, please ensure that the value is present in the connector space"); // } log.Trace("Adding user to FIM - {0}", mySamAccountName); using (AxaptaRecord user = DynAx.CreateAxaptaRecord("UserInfo")) { try { log.Trace("Values being Added. Name {0} | Company {1} | Id {2} | network Domain {3} | network Alias {4} | object Sid {5}", myFull, myCo, myId, myNetworkDomain, myNetworkDomain, mySamAccountName, myObjectSid.ToString()); user.set_Field("name", myFull); //user.set_Field("company", myCo); user.set_Field("id", myId); user.set_Field("networkDomain", myNetworkDomain); user.set_Field("networkAlias", mySamAccountName); user.set_Field("sid", myObjectSid); //user.set_Field("enabled", 1); user.Insert(); log.Trace("Committed Add"); } catch (Exception ex) { //csentryChange.ErrorCodeExport = MAExportError.ExportErrorConnectedDirectoryError; log.ErrorException("Failure with adding a user", ex); } } } private string GenerateId(string baseId) { using (AxaptaRecord user = DynAx.CreateAxaptaRecord("UserInfo")) { string query = string.Format("select * from %1 where id = {0} ", baseId); DynAx.ExecuteStmt(query, user); if (string.IsNullOrEmpty(user.get_Field("Name").ToString())) { return baseId; } else { var firstThreeCharacters = baseId.Substring(0, 3); var lastCharacter = baseId.Substring(3, 1); int hold = 0; int.TryParse(lastCharacter, out hold); hold++; return GenerateId(firstThreeCharacters + hold.ToString()); } } } /// <summary> /// </summary> /// <param name="baseId"></param> /// <returns></returns> /// private string TransformSid(string mvSid) { if (string.IsNullOrEmpty(mvSid)) { return mvSid; } else { byte[] binarySid = Convert.FromBase64String(mvSid); var sddlSid = new SecurityIdentifier(binarySid, 0); log.Trace("Metaverse: Writing objectSid value {0}", sddlSid.AccountDomainSid.ToString()); return TransformSid(sddlSid.AccountDomainSid.ToString()); } } }; }