Extensible Connectivity 2.0 Management Agent
Extensible Connectivity 2.0 Management Agent
0
Management Agent
Document History
Date 8/12/2012
Document No. -
Version number 1.1
Change Record
Reviewers
This document contains technical knowledge of creating Extensible Connectivity 2.0 Management Agent
for Microsoft Forefront Identity Manager 2010 R2.
Microsoft Forefront Identity Manager comes with a variety of OOB Management Agents that are and can
be used to connect to a verity of Microsoft and third party applications. However, if any third party
application does not have an OOB Management Agent included in FIM 2010 R2, we can create that
management agent with the help of the Extensible Connectivity 2.0 Management Agent Framework that is
provided by FIM 2010 R2.
As an example for a Call Based ECMA, an Access Extensible Connectivity Management Agent has been
created. As an example for a File Based ECMA, an XML Extensible Connectivity Management Agent has
been created.
Also along with the Export and Import of data to and from FIM 2010 R2, the code examples in this
document also implement the Password Extensions which enable real-time password synchronization with
the Active Directory.
Audience
This document is for all those who are interested in creating Custom Management Agents for the
Microsoft Forefront Identity Manager 2010 R2.
As this is a technical document, some knowledge of Microsoft FIM 2010 R2 is required to understand the
contents. The reader should also have knowledge of Microsoft Visual Studio 2010 and should know the
C# Programming Language.
A prior programming experience, most preferably in C#, is clearly required to implement the steps
underlined in this document.
Extensible Connectivity Management Agent 2.0
The Management Agent for Extensible Connectivity 2.0 (ECMA 2.0) is a new management agent for FIM
2010 that contains many features unavailable in existing management agents. Similar to the Management
Agent for Extensible Connectivity that is included in FIM 2010, the ECMA 2.0 can be customized for any
data source, and can be packaged and distributed to other FIM environments. In addition, the ECMA 2.0
1. Can implement a call-based import and export, as well as a file-based import and export
3. Runs in design mode and packaged mode for ease of configuration in Synchronization Service Manager
7. Provides a new streamlined template for creating extension projects in Microsoft Visual Studio
Depending on the requirements you can select the Programming Language (Visual Basic .NET or Visual
C#), Visual Studio Version (Visual Studio 2008 or Visual Studio 2010), Project Name and Project Location.
Note: The project name that you provide here is what you will get in the list when you are
creating the MA.
When the Visual Studio launches the following initial project is created. According to the requirements,
the programmer has to insert code. The code for the Call Based and File Based Management Agent are
both included in this document, however the programmer will have to tweak the code or recreate it if real
world implementations.
Once the code has been written and compiled successfully, the Extensible Connectivity Management
Agent can be created in FIM 2010.
A File Based ECMA is more simple and easier to code and develop. However it is not the suitable choice
for an ECMA when a Call Based ECMA can be developed for the external system. Thus a File Based ECMA
should only be developed when a Call Based ECMA cannot be created. A File Based ECMA is advised for
External Systems that do not support external access or interface to the system or its low level database.
Moreover a File Based ECMA can be developed for a system that supports internal scripts or GUI to
import or export files or can run scripts internally to read external files and process the commands therein
accordingly.
Following are the code examples for both a Call Based ECMA and a File Based ECMA.
using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Microsoft.MetadirectoryServices;
namespace CallBasedMA
{
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 System.Data.OleDb;
using System.Data;
namespace FimSync_Ezma
{
public class EzmaExtension :
IMAExtensible2CallExport,
IMAExtensible2CallImport,
// IMAExtensible2FileImport,
//IMAExtensible2FileExport,
//IMAExtensible2GetHierarchy,
IMAExtensible2GetSchema,
IMAExtensible2GetCapabilities,
IMAExtensible2GetParameters,
IMAExtensible2Password
//IMAExtensible2GetPartitions
{
private int m_importDefaultPageSize = 12;
private int m_importMaxPageSize = 50;
private int m_exportDefaultPageSize = 10;
private int m_exportMaxPageSize = 20;
public string myConnection;
public string myFile;
public string myTable;
OleDbConnection conn;
OleDbCommand cmd;
OleDbDataAdapter adapter;
DataSet da;
public string myFirst;
public string myLast;
public string myEmail;
public string myEmpID;
public string myFull;
public string myAccount;
//
// Constructor
//
public EzmaExtension()
{
//
// TODO: Add constructor logic here
//
WriteException.WriteExceptionToFile("In the Constructor");
}
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;
}
}
public IList<ConfigParameterDefinition>
GetConfigParameters(KeyedCollection<string, ConfigParameter>
configParameters,
ConfigParameterPage page)
{
List<ConfigParameterDefinition> configParametersDefinitions = new
List<ConfigParameterDefinition>();
switch (page)
{
case ConfigParameterPage.Connectivity:
configParametersDefinitions.Add(ConfigParameterDefinition.Cr
eateStringParameter("File", ""));
WriteException.WriteExceptionToFile("GetConfigParameters:
File");
configParametersDefinitions.Add(ConfigParameterDefinition.Cr
eateStringParameter("Table", ""));
WriteException.WriteExceptionToFile("GetConfigParameters:
Table");
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();
WriteException.WriteExceptionToFile(string.Format("ValidateC
onfigParameters: myResults: {0}, {1}, {2}, {3}",
myResults.Code, myResults.ErrorMessage,
myResults.ErrorParameter, myResults.ToString()));
return myResults;
}
public Schema GetSchema(KeyedCollection<string, ConfigParameter>
configParameters)
{
WriteException.WriteExceptionToFile("In Get Schema");
Microsoft.MetadirectoryServices.SchemaType personType =
Microsoft.MetadirectoryServices.SchemaType.Create("AccessUse
r", false);
myFile = configParameters["File"].Value;
WriteException.WriteExceptionToFile(myFile);
myTable = configParameters["Table"].Value;
WriteException.WriteExceptionToFile(myTable);
DataSet myData = this.AccessSchema(myFile, myTable);
WriteException.WriteExceptionToFile("Got Schema!");
string[] SQLSchema = new
string[myData.Tables["Columns"].Rows.Count];
for (int i = 0; i <= myData.Tables["Columns"].Rows.Count ‐ 1;
i++)
{
SQLSchema[i] =
myData.Tables["Columns"].Rows[i].ItemArray.GetValue(0).ToStr
ing().Trim();
string myattrib = SQLSchema[i];
if (myattrib == "EmployeeID")
{
personType.Attributes.Add(SchemaAttribute.CreateAnchorAttrib
ute(myattrib, AttributeType.String));
WriteException.WriteExceptionToFile("in EmployeeID Check:
" + myattrib);
}
else
{
personType.Attributes.Add(SchemaAttribute.CreateSingleValued
Attribute(myattrib, AttributeType.String));
WriteException.WriteExceptionToFile("in EmployeeID Else
Check: " + myattrib);
}
}
Schema schema = Schema.Create();
schema.Types.Add(personType);
WriteException.WriteExceptionToFile("Returning Schema!!!");
return schema;
}
public DataSet AccessSchema(string file, string table)
{
WriteException.WriteExceptionToFile("In Access Schema!!!");
try
{
myConnection = ("Provider=Microsoft.ACE.OLEDB.12.0;Data
Source=" + file + ";Persist Security Info=False;");
WriteException.WriteExceptionToFile(myConnection);
try
{
conn = new OleDbConnection(myConnection);
conn.Open();
WriteException.WriteExceptionToFile("Connection OK!!");
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
cmd = new OleDbCommand();
cmd.CommandType = CommandType.Text;
string cmdText = "Select * from " + table + "";
WriteException.WriteExceptionToFile(cmdText);
cmd.CommandText = cmdText;
cmd.Connection = conn;
WriteException.WriteExceptionToFile("Assigned connection to
Command!");
adapter = new OleDbDataAdapter(cmd);
DataTable dt1 = new DataTable();
adapter.Fill(dt1);
WriteException.WriteExceptionToFile("Adapter Filled");
DataTable dt2 = new DataTable("Columns");
dt2.Columns.Add("COLUMN_NAME", typeof(string));
for (int i = 0; i < dt1.Columns.Count; i++)
{
dt2.Rows.Add(dt2.NewRow());
WriteException.WriteExceptionToFile(dt1.Columns[i].ColumnNam
e);
dt2.Rows[dt2.Rows.Count ‐ 1][0] =
dt1.Columns[i].ColumnName;
}
da = new DataSet();
da.Tables.Add(dt2);
return da;
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
return null;
}
public OpenImportConnectionResults OpenImportConnection(
KeyedCollection<string,
ConfigParameter> configParameters,
Schema types,
OpenImportConnectionRunStep
importRunStep)
{
myFile = configParameters["File"].Value;
myTable = configParameters["Table"].Value;
myConnection = ("Provider=Microsoft.ACE.OLEDB.12.0;Data Source="
+ myFile + ";Persist Security Info=False;");
conn = new OleDbConnection(myConnection);
cmd = new OleDbCommand();
cmd.CommandType = CommandType.Text;
string cmdText = "Select * from " + myTable;
cmd.CommandText = cmdText;
cmd.Connection = conn;
return new OpenImportConnectionResults();
}
public GetImportEntriesResults
GetImportEntries(GetImportEntriesRunStep importRunStep)
{
adapter = new OleDbDataAdapter(cmd);
da = new DataSet();
adapter.Fill(da, "Employees");
GetImportEntriesResults importReturnInfo;
List<CSEntryChange> csentries = new List<CSEntryChange>();
for (int i = 0; i <= da.Tables["Employees"].Rows.Count ‐ 1; i++)
{
CSEntryChange csentry1 = CSEntryChange.Create();
csentry1.ObjectModificationType = ObjectModificationType.Add;
csentry1.ObjectType = "AccessUser";
for (int j = 0; j < da.Tables["Employees"].Columns.Count;
j++)
{
csentry1.AttributeChanges.Add(AttributeChange.CreateAttribut
eAdd(da.Tables["Employees"].Columns[j].ColumnName,
da.Tables["Employees"].Rows[i].ItemArray.GetValue(j).ToStrin
g().Trim()));
}
csentries.Add(csentry1);
}
importReturnInfo = new GetImportEntriesResults();
importReturnInfo.MoreToImport = false;
importReturnInfo.CSEntries = csentries;
return importReturnInfo;
}
public CloseImportConnectionResults
CloseImportConnection(CloseImportConnectionRunStep
importRunStepInfo)
{
conn.Close();
return new CloseImportConnectionResults();
}
public int ImportMaxPageSize
{
get
{
return m_importMaxPageSize;
}
}
public int ImportDefaultPageSize
{
get
{
return m_importDefaultPageSize;
}
}
public void OpenExportConnection(KeyedCollection<string,
ConfigParameter> configParameters,
Schema types,
OpenExportConnectionRunStep exportRunStep)
{
myFile = configParameters["File"].Value;
myTable = configParameters["Table"].Value;
myConnection = ("Provider=Microsoft.ACE.OLEDB.12.0;Data Source="
+ myFile + ";Persist Security Info=False;");
conn = new OleDbConnection(myConnection);
cmd = new OleDbCommand();
cmd.CommandType = CommandType.Text;
conn.Open();
}
public PutExportEntriesResults PutExportEntries(IList<CSEntryChange>
csentries)
{
int i = 0;
foreach (CSEntryChange csentryChange in csentries)
{
myEmpID = csentries[i].DN.ToString();
if (csentryChange.ObjectType == "AccessUser")
{
#region Add
if (csentryChange.ObjectModificationType ==
ObjectModificationType.Add)
{
#region a
foreach (string attrib in
csentryChange.ChangedAttributeNames)
{
switch (attrib)
{
case "FirstName":
myFirst =
csentryChange.AttributeChanges["FirstName"].ValueChanges[0].
Value.ToString();
break;
case "LastName":
myLast =
csentryChange.AttributeChanges["LastName"].ValueChanges[0].V
alue.ToString();
break;
case "EMail":
myEmail =
csentryChange.AttributeChanges["EMail"].ValueChanges[0].Valu
e.ToString();
break;
case "AccountName":
myAccount =
csentryChange.AttributeChanges["AccountName"].ValueChanges[0
].Value.ToString();
break;
case "FullName":
myFull =
csentryChange.AttributeChanges["FullName"].ValueChanges[0].V
alue.ToString();
break;
}
}
#endregion
string cmdText = "Insert into " + myTable +
"(FirstName, LastName, EMail, EmployeeID, FullName,
AccountName) VALUES ('" + myFirst + "','" + myLast + "','" +
myEmail + "','" + myEmpID + "','" + myFull + "','" +
myAccount + "')";
cmd.CommandText = cmdText;
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
#endregion
#region Delete
if (csentryChange.ObjectModificationType ==
ObjectModificationType.Delete)
{
myEmpID = csentries[i].DN.ToString();
string cmdText = "Delete from " + myTable + "Where
EmployeeID = '" + myEmpID + "'";
cmd.CommandText = cmdText;
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
#endregion
#region Update
if (csentryChange.ObjectModificationType ==
ObjectModificationType.Update)
{
foreach (string attribName in
csentryChange.ChangedAttributeNames)
{
if
(csentryChange.AttributeChanges[attribName].ModificationType
== AttributeModificationType.Add)
{
myEmpID =
csentryChange.AnchorAttributes[0].Value.ToString();
string attribValue =
csentryChange.AttributeChanges[attribName].ValueChanges[0].V
alue.ToString();
string cmdText = "Update" + myTable + "SET" +
attribName + " = '" + attribValue + "' Where EmployeeID = '"
+ myEmpID + "'";
cmd.CommandText = cmdText;
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
else if
(csentryChange.AttributeChanges[attribName].ModificationType
== AttributeModificationType.Delete)
{
myEmpID =
csentryChange.AnchorAttributes[0].Value.ToString();
string cmdText = "Update " + myTable + " SET
" + attribName + " = 'NULL' Where EmployeeID = '" + myEmpID
+ "'";
cmd.CommandText = cmdText;
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
else if
(csentryChange.AttributeChanges[attribName].ModificationType
== AttributeModificationType.Replace)
{
myEmpID =
csentryChange.AnchorAttributes[0].Value.ToString();
string attribValue =
csentryChange.AttributeChanges[attribName].ValueChanges[0].V
alue.ToString();
string cmdText = "Update " + myTable + " SET
" + attribName + " = '" + attribValue + "' Where EmployeeID
= '" + myEmpID + "'";
cmd.CommandText = cmdText;
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
else if
(csentryChange.AttributeChanges[attribName].ModificationType
== AttributeModificationType.Update)
{
myEmpID =
csentryChange.AnchorAttributes[0].Value.ToString();
string attribValue =
csentryChange.AttributeChanges[attribName].ValueChanges[0].V
alue.ToString();
string cmdText = "Update " + myTable + " SET
" + attribName + " = '" + attribValue + "' Where EmployeeID
= '" + myEmpID + "'";
cmd.CommandText = cmdText;
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
}
}
#endregion
}
i++;
}
PutExportEntriesResults exportEntriesResults = new
PutExportEntriesResults();
return exportEntriesResults;
}
public void CloseExportConnection(CloseExportConnectionRunStep
exportRunStep)
{
conn.Close();
}
public int ExportDefaultPageSize
{
get
{
return m_exportDefaultPageSize;
}
set
{
m_exportDefaultPageSize = value;
}
}
public int ExportMaxPageSize
{
get
{
return m_exportMaxPageSize;
}
set
{
m_exportMaxPageSize = value;
}
}
///////////////////////////////////// Password Management
/////////////////////////////////
OleDbCommand cmdP;
OleDbConnection connP;
public void OpenPasswordConnection(KeyedCollection<string,
ConfigParameter> configParameters, Partition partition)
{
//
// TODO: Remove this throw statement if you implement this method
//
//throw new EntryPointNotImplementedException();
try
{
string myFile = @"E:\Projects\FIM\Users.mdb";
//WriteException.WriteExceptionToFile(myFile);
string myConnection =
("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + myFile +
";Persist Security Info=False;");
connP = new OleDbConnection(myConnection);
connP.Open();
cmdP = new OleDbCommand();
cmdP.CommandType = CommandType.Text;
cmdP.Connection = connP;
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public void ClosePasswordConnection()
{
try
{
cmdP.Dispose();
connP.Close();
connP.Dispose();
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public ConnectionSecurityLevel GetConnectionSecurityLevel(
)
{
return ConnectionSecurityLevel.NotSecure;
}
public void SetPassword(
CSEntry csentry,
System.Security.SecureString newPassword,
PasswordOptions options
)
{
try
{
IntPtr StringPointer =
System.Runtime.InteropServices.Marshal.SecureStringToBSTR(ne
wPassword);
string newPasswordStr =
System.Runtime.InteropServices.Marshal.PtrToStringBSTR(Strin
gPointer);
cmdP.CommandText = "UPDATE Users SET [Password] = '" +
newPasswordStr + "' WHERE EmployeeID = '" +
csentry["EmployeeID"].StringValue + "'";
WriteException.WriteExceptionToFile(cmdP.CommandText);
cmdP.ExecuteNonQuery();
WriteException.WriteExceptionToFile("Password change
Successfully for User: " +
csentry["EmployeeID"].StringValue);
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public void ChangePassword(CSEntry csentry,
System.Security.SecureString OldPassword,
System.Security.SecureString NewPassword)
{
try
{
IntPtr StringPointerNew =
System.Runtime.InteropServices.Marshal.SecureStringToBSTR(Ne
wPassword);
string newPasswordStr =
System.Runtime.InteropServices.Marshal.PtrToStringBSTR(Strin
gPointerNew);
IntPtr StringPointerOld =
System.Runtime.InteropServices.Marshal.SecureStringToBSTR(Ol
dPassword);
string oldPasswordStr =
System.Runtime.InteropServices.Marshal.PtrToStringBSTR(Strin
gPointerOld);
cmdP.CommandText = "UPDATE Users SET Password = '" +
newPasswordStr + "' WHERE EmployeeID = '" +
csentry["EmployeeID"].StringValue + "' AND Password = '" +
oldPasswordStr + "'";
cmdP.ExecuteNonQuery();
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public void RequireChangePasswordOnNextLogin(
CSEntry csentry,
bool fRequireChangePasswordOnNextLogin
)
{
//throw new EntryPointNotImplementedException();
}
};
}
public static class WriteException
{
static bool DEBUG = false;
public static void WriteExceptionToFile(string strException)
{
if (!DEBUG) return;
File.AppendAllText("E:\\Projects\\exception.txt", strException +
"\r\n");
}
}
}
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;
namespace FimSync_Ezma
{
public class EzmaExtension :
//IMAExtensible2CallExport,
//IMAExtensible2CallImport,
IMAExtensible2FileImport,
IMAExtensible2FileExport,
//IMAExtensible2GetHierarchy,
//IMAExtensible2GetSchema,
IMAExtensible2GetCapabilities,
IMAExtensible2GetParameters,
IMAExtensible2Password
//IMAExtensible2GetPartitions
{
//
// Constructor
//
public EzmaExtension()
{
//
// TODO: Add constructor logic here
//
}
public MACapabilities Capabilities
{
get
{
MACapabilities myCapabilities = new MACapabilities();
myCapabilities.ConcurrentOperation = true;
myCapabilities.ObjectRename = false;
myCapabilities.DeleteAddAsReplace = true;
myCapabilities.DeltaImport = true;
myCapabilities.DistinguishedNameStyle =
MADistinguishedNameStyle.None;
myCapabilities.ExportType = MAExportType.AttributeUpdate;
myCapabilities.NoReferenceValuesInFirstExport = false;
myCapabilities.Normalizations = MANormalizations.None;
return myCapabilities;
}
}
public IList<ConfigParameterDefinition>
GetConfigParameters(KeyedCollection<string, ConfigParameter>
configParameters,
ConfigParameterPage
page)
{
List<ConfigParameterDefinition> configParametersDefinitions = new
List<ConfigParameterDefinition>();
switch (page)
{
case ConfigParameterPage.Connectivity:
break;
case ConfigParameterPage.Global:
configParametersDefinitions.Add(ConfigParameterDefinition.Cr
eateStringParameter("Full Import File", ""));
configParametersDefinitions.Add(ConfigParameterDefinition.Cr
eateStringParameter("Delta Import File", ""));
configParametersDefinitions.Add(ConfigParameterDefinition.Cr
eateStringParameter("Export File", ""));
configParametersDefinitions.Add(ConfigParameterDefinition.Cr
eateDropDownParameter("Export Encoding",
"ASCII,Unicode,UTF8", false, "UTF8"));
break;
case ConfigParameterPage.Partition:
break;
case ConfigParameterPage.RunStep:
configParametersDefinitions.Add(ConfigParameterDefinition.Cr
eateCheckBoxParameter("Omit Xml Declaration", false));
break;
}
return configParametersDefinitions;
}
public ParameterValidationResult
ValidateConfigParameters(KeyedCollection<string,
ConfigParameter> configParameters,
ConfigParameterPage page)
{
ParameterValidationResult myResults = new
ParameterValidationResult();
return myResults;
}
public WriteImportFileResults WriteImportFile(
KeyedCollection<string,
ConfigParameter> configParameters,
Schema types,
WriteImportFileRunStep
importRunStep)
{
try
{
StreamWriter swImport = new StreamWriter(importRunStep.FilePath,
true);
swImport.WriteLine(
"{0},{1},{2},{3},{4},{5}",
"objectclass",
"delta",
"EmployeeID",
"FirstName",
"LastName",
"Email"
);
string inputxml = "";
XmlDocument doc = new XmlDocument();
if (importRunStep.ImportType == OperationType.Full)
{
inputxml = MAUtils.MAFolder + @"\" + configParameters["Full
Import File"].Value;
}
if (importRunStep.ImportType == OperationType.Delta)
{
inputxml = MAUtils.MAFolder + @"\" + configParameters["Delta
Import File"].Value;
}
doc.Load(inputxml);
XmlNodeList xnList = doc.SelectNodes("/Users/object");
foreach (XmlNode node in xnList)
{
string objectclass = node["objectclass"].InnerText;
string delta = node["delta"].InnerText;
string EmployeeID = node["EmployeeID"].InnerText;
string FirstName = node["FirstName"].InnerText;
string LastName = node["LastName"].InnerText;
string Email = node["Email"].InnerText;
string fullline = objectclass + "," + delta + "," +
EmployeeID + "," + FirstName + "," + LastName + "," + Email;
swImport.WriteLine(fullline);
}
swImport.WriteLine();
swImport.Close();
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
return new WriteImportFileResults();
}
public void ReadExportFile(KeyedCollection<string, ConfigParameter>
configParameters,
Schema types,
ReadExportFileRunStep exportRunStep)
{
XmlWriter m_xmlWriterExport;
StreamReader sr = new StreamReader(exportRunStep.FilePath);
string lineContents = null;
XmlWriterSettings xmlSettings = new XmlWriterSettings();
string encoding = configParameters["Export Encoding"].Value;
if (encoding.Equals("ASCII"))
{
xmlSettings.Encoding = Encoding.ASCII;
}
else if (encoding.Equals("UTF8"))
{
xmlSettings.Encoding = Encoding.UTF8;
}
else
{
xmlSettings.Encoding = Encoding.Unicode;
}
string omitXmlDecl = configParameters["Omit Xml Declaration"].Value;
if (omitXmlDecl.Equals("1"))
{
xmlSettings.OmitXmlDeclaration = true;
}
string exportfile = configParameters["Export File"].Value.ToString();
m_xmlWriterExport = XmlTextWriter.Create(MAUtils.MAFolder + @"\" +
exportfile, xmlSettings);
m_xmlWriterExport.WriteStartElement(Nodes.Root);
m_xmlWriterExport.WriteAttributeString(Nodes.FullExport,
(OperationType.Full == exportRunStep.ExportType) ? "true" :
"false");
m_xmlWriterExport.WriteElementString(Nodes.PartitionDN,
exportRunStep.StepPartition.DN);
while (null != (lineContents = sr.ReadLine()))
{
char[] commaEscape = new char[] { ',' };
char[] quoteEscape = new char[] { '"' };
string[] valueComponents = lineContents.Split(commaEscape);
if (Nodes.ObjectClass == valueComponents[0].Trim(quoteEscape))
{
continue;
}
m_xmlWriterExport.WriteStartElement(Nodes.Object);
m_xmlWriterExport.WriteElementString(
Nodes.ObjectClass,
valueComponents[0].Trim(quoteEscape)
);
m_xmlWriterExport.WriteElementString(
Nodes.Delta,
valueComponents[1].Trim(quoteEscape)
);
m_xmlWriterExport.WriteElementString(
Nodes.EmployeeID,
valueComponents[2].Trim(quoteEscape)
);
m_xmlWriterExport.WriteElementString(
Nodes.FirstName,
valueComponents[3].Trim(quoteEscape)
);
m_xmlWriterExport.WriteElementString(
Nodes.LastName,
valueComponents[4].Trim(quoteEscape)
);
m_xmlWriterExport.WriteElementString(
Nodes.Email,
valueComponents[5].Trim(quoteEscape)
);
m_xmlWriterExport.WriteEndElement();
}
m_xmlWriterExport.WriteEndElement();
m_xmlWriterExport.Close();
}
struct Nodes
{
public const string Root = "Users";
public const string PartitionDN = "partition‐dn";
public const string FullExport = "full‐export";
public const string Object = "object";
public const string ObjectClass = "objectclass";
public const string Delta = "delta";
public const string EmployeeID = "EmployeeID";
public const string FirstName = "FirstName";
public const string LastName = "LastName";
public const string Email = "Email";
}
///////////////////////////////////// Password Management
/////////////////////////////////
OleDbCommand cmdP;
OleDbConnection connP;
public void OpenPasswordConnection(KeyedCollection<string,
ConfigParameter> configParameters, Partition partition)
{
//
// TODO: Remove this throw statement if you implement this method
//
//throw new EntryPointNotImplementedException();
try
{
string myFile = @"E:\Projects\FIM\Users.mdb";
//WriteException.WriteExceptionToFile(myFile);
string myConnection =
("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + myFile +
";Persist Security Info=False;");
connP = new OleDbConnection(myConnection);
connP.Open();
cmdP = new OleDbCommand();
cmdP.CommandType = CommandType.Text;
cmdP.Connection = connP;
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public void ClosePasswordConnection()
{
try
{
cmdP.Dispose();
connP.Close();
connP.Dispose();
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public ConnectionSecurityLevel GetConnectionSecurityLevel(
)
{
return ConnectionSecurityLevel.NotSecure;
}
public void SetPassword(
CSEntry csentry,
System.Security.SecureString newPassword,
PasswordOptions options
)
{
try
{
IntPtr StringPointer =
System.Runtime.InteropServices.Marshal.SecureStringToBSTR(ne
wPassword);
string newPasswordStr =
System.Runtime.InteropServices.Marshal.PtrToStringBSTR(Strin
gPointer);
cmdP.CommandText = "UPDATE Users SET [Password] = '" +
newPasswordStr + "' WHERE EmployeeID = '" +
csentry["EmployeeID"].StringValue + "'";
WriteException.WriteExceptionToFile(cmdP.CommandText);
cmdP.ExecuteNonQuery();
WriteException.WriteExceptionToFile("Password change
Successfully for User: " +
csentry["EmployeeID"].StringValue);
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public void ChangePassword(CSEntry csentry,
System.Security.SecureString OldPassword,
System.Security.SecureString NewPassword)
{
try
{
IntPtr StringPointerNew =
System.Runtime.InteropServices.Marshal.SecureStringToBSTR(Ne
wPassword);
string newPasswordStr =
System.Runtime.InteropServices.Marshal.PtrToStringBSTR(Strin
gPointerNew);
IntPtr StringPointerOld =
System.Runtime.InteropServices.Marshal.SecureStringToBSTR(Ol
dPassword);
string oldPasswordStr =
System.Runtime.InteropServices.Marshal.PtrToStringBSTR(Strin
gPointerOld);
cmdP.CommandText = "UPDATE Users SET Password = '" +
newPasswordStr + "' WHERE EmployeeID = '" +
csentry["EmployeeID"].StringValue + "' AND Password = '" +
oldPasswordStr + "'";
cmdP.ExecuteNonQuery();
}
catch (Exception exp)
{
WriteException.WriteExceptionToFile(exp.ToString());
}
}
public void RequireChangePasswordOnNextLogin(
CSEntry csentry,
bool fRequireChangePasswordOnNextLogin
)
{
//throw new EntryPointNotImplementedException();
}
};
public static class WriteException
{
static bool DEBUG = true;
public static void WriteExceptionToFile(string strException)
{
if (!DEBUG) return;
File.AppendAllText("E:\\Projects\\exception.txt", strException +
"\r\n");
}
}
}
Note: This document assumes that your development platform is on the FIM 2010 R2
Synchronization Server itself. If your development environment is located into another machine
not hosting the FIM 2010 R2 Synchronization Service, you will have to configure or copy your dll
manually to the above location of the FIM 2010 R2 Synchronization Server, to be able to create
the FIM Extensible Management Agent 2.0.
Please follow the screen shots below to understand how to create the ECMA on the FIM 2010 R2
Synchronization Service GUI.
Password Change Notification
For the Password Management for any MA where the AD has to be the Source of managing passwords,
the Password Change Notification Service has to be installed and configured on all the domain
controllers.
For download this service and more information on PCNS (Password Change Notification Service) please
follow the below link.
http://www.microsoft.com/en-us/download/details.aspx?id=19495
http://technet.microsoft.com/en-us/library/cc720654(WS.10).aspx
Note: Please note that the PCNS Service should be Installed on ALL the domain controllers.
Failure to do so shall not record password changes on the domain controller that the service is
not running on.
Following is the setup and configuration of PCNS relevant to the test environment used in this document.
Setspn.exe ‐a PassChangeNS/fimtestdc.fimtest.com fimtest\FIMServiceAccount
Usage:
Setspn.exe ‐a <user defined named for target FIM 2010 R2 server>/<fully qualified
domain name of the server running FIM 2010 R2>\<domain\user
name of the FIM 2010 R2 service account>
For example: Setspn.exe ‐a PCNSCLNT/fab‐dev‐01.usergroup.fabrikam.com fab‐dev‐
01\MIISServAccount
Pcnscfg.exe addtarget /n:PassChangeNSCfg /a:FIMTestDC.fimtest.com
/s:PassChangeNS/fimtestdc.fimtest.com /fi:"Domain Users"
/f:3
Usage:
Pcnscfg.exe addtarget /n:<user‐defined friendly name of the target server running
FIM 2010 R2> /a:<fully‐qualified domain name of the server
running FIM 2010 R2> /s:<the SPN for the FIM 2010 R2 server>
/fi:<the specified inclusion group> /f:3
For example:
Pcnscfg.exe addtarget /n:miisdemo /a:fab‐dev‐01.usergroup.fabrikam.com
/s:PCNSCLNT/fab‐dev‐01.usergroup.fabrikam.com /fi:“Domain
Users” /f:3
The existence and format of the dn attribute is defined by the MA Capability dnStyle. dnStyle can have the
following values:
Value Description
None The management agent does not have a separate dn attribute. The anchor is used for
references between objects. Objects cannot be renamed.
Generic The management agent has a dn. It can be of type string, integer or binary but it is
not in LDAP style. Object renames are allowed.
LDAP The management agent has a dn and it is of LDAP style. An LDAP style dn allows the
interface GetHierarchy to be used and that Provisioning Hierarchy is enabled. Object
renames are allowed.
Connected Data Sources that Expect server to Provide the Anchor Attributes
Connected Data Sources that Generate the Anchor Attributes
Connected Data Sources that Generate the Anchor Attributes
The MA’s export code will also be different depending on the scenario:
Anchor attributes cannot be changed in provisioning code or EAF once the object has been exported.
The following table lists errors the Sync Engine can return with respect to anchor issues.
Error Description
E_MMS_MA_ANCHOR_TOO_LONG
The following table lists errors the Sync Engine can return with respect to dn issues.
Error Description
If a change to the dn is detected and the dnStyle is Generic or LDAP then this is treated as an object
rename.
Depending on the error the step run result or WMI return message will be different.
Export Errors
Special action. An error where the sync engine should take an action to correct the problem.
These will not be visible in the UI.
Object level error visible in the UI
Import Errors
Discovery error which tell the sync engine to not commit the watermark but continue the run.
Discovery error which is a fatal import error and stops the run.
Connection errors
Connectivity issues which will stop the run.
Error from the Sync Engine to the MA.
Export Errors, Special Actions
The following list can be returned on an object level during ExportEntries. These also have a
corresponding object level error when the sync engine is not supposed to take a special action.
All these errors will put the object into the next export pass.
Error Notes
Error Notes
cd-error This error is returned when the connected data source has a specialized error
type. This error is accompanied by the <cd-error> element, and the
information contained there should aid in troubleshooting.
cd-existing-object This error is returned when an add is exported to the connected data source,
but the object is already present in the connected data source.
dn-attributes-failure This error is returned when exporting an add or modify sets a reference value
for which there is no corresponding connected data source object. If you see
this error, use the connector space object viewer to determine which changes
to reference attributes were not successfully exported.
Non-existent-parent This error is returned by the management agent for LDAP when either the
export of an add or a rename fails because the parent object does not exist in
the connected data source.
Discovery errors, continue run
The following list can be returned on an object level during ImportEntries. These will be reported as errors
in the UI. When the first object with a discovery error is returned the watermark will not be persisted for
the remainder of the run.
Error Notes
missing-dn It indicates that the management agent could read the element and
parse it, but there was no domain name value for the object.
dn-not-ldap-conformant This error is returned when a management agent reports a domain name
value that does not conform to the LDAP specification.
invalid-dn This error is returned when a management agent reports that a domain
name does not meet an FIM constraint.
Most discovery errors will be detected by the Sync Engine when it is trying to persist the connector space
object. These will be visible in the UI but they are not available for the MA to consume.
Error Notes
parse-error This error is returned in delta mode when it cannot parse an entry.
read-error This error is returned by call-based management agents when there is a generic error
reading a particular object.
Error Notes
stopped-server-down The run step stopped because the data source server is down.
stopped-extension-dll-exception The run step stopped because the extension DLL threw an
exception.
Exceptions
The following is a list of exceptions that can be thrown by the Extensible Connectivity 2.0 Management
Agent.
BadServerCredentialsException The credentials that are used to connect to the connected directory
are not valid. When this exception is thrown by this method, the run
stops and the WMI Provider returns the string stopped-bad-server-
credentials.
EntryPointNotImplementedException The rules extension does not implement this method. When this
exception is thrown by this method, the run stops and the WMI
Provider returns the string stopped-entry-point-not-implemented.
PasswordPolicyViolationException Thrown when the password specified violates the password policy.
General Information
The following is a list of general information about using the ECMA 2.0.
Attribute names can only contain the a‐z, A‐Z, 0‐9, and ‘‐’
Attribute names currently can only be composed of the following a-z, A-Z, 0-9, and ‘-‘.
LDAP style DN's on File based MA's, will not utilize the GetHeirarchy() interface.
Known Issues
The following is a list of known issues.
Can create an Import run profile for an Export Only MA and run without error.
If you create an Export-only Management Agent, but create an Import run profile for it, you will not get an
error or a cancelation of the run when you use it. Instead the MA will show a run status of ‘in-progress’
until the admin cancels it manually.
Delete Export on call‐based attribute‐update exports on anchors will result in superfluous information
being returned
If the developer is doing a Delete during export on a call-based, attribute-update type export, they could
potentially see superfluous Attribute and Value Modification Types for the anchor attribute. These extra
pieces of information should just be ignored by the Management Agent, as they are not needed for the
attribute delete.
Unable to get past Global Parameters page if Connectivity Parameters is not defined.
If you implement an ECMA 2.0 that does not have any Connectivity Parameters defined but does have
Global Parameters defined, you may not be able to get past the Global Parameters page if you refresh the
dll. The reason this is happening is because it is up to the connectivity page to update the global
parameters. With no connectivity page, we asked for a refresh of the global parameters but don't have the
means to refresh global parameters at all.
MissingParentObjectException
If you provision an object to a call based ECMA 2 that is using Generic DN style and that object has a DN
that is modeled after LDAP DNs (ex. CN=User,OU=Test,DC=microsoft,DC=com) you will get a
MissingParentObjectException.
The reason this occurs is that even though you set up Generic DN Style, the DN capability you set still will
take into account hierarchy in this mode. Specifically it looks for commas. To workaround this, do not put
commas in your DN name when using Generic style DN.