Creating a SOAP XML Webservice to update HR DB via Export from FIM 2010 R2/MIM 2016 ECMA2 connector

We have already talked about how to create a Webservice to pull HR data and consume the data via FIM/MIM ECMA2, that blogpost can be found here. Now I want to talk about if you want to write back to HR system, for instance Active Directory will supply Email info back to HR or Windows ID or EnterpriseID information, all data that AD is authoritative for. If you have already done the exercise of setting up your Webservice to pull the employee data then this will be easy, there are just a few adjustments.

Let’s begin

Overall Test environment

I have a Windows 2012 R2 server which is going to host the Webservice. I would advise that you don’t put the Webservice on the same IIS server that you have the FIM Portal else you will spend hours trying to decipher why it isn’t working. The root cause has to do with the fact that FIM Portal takes port 80 and causes conflicts. My FIM 2010 R2 server is on a windows 2008 R2 server, my Server is on DotNet 4.0. My SQL 2008 R2 database is on my FIM server.

Creating the Contoso database

  1. Go to SQL and create a database called “CONTOSO”
  2. Create a table called HR, you can use this script in the query window to create it.

USE [CONTOSO]
GO
/****** Object: Table [dbo].[HR] Script Date: 11/13/2016 7:43:45 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[HR](
[FirstName] [char](20) NULL,
[LastName] [char](30) NULL,
[EMail] [nchar](30) NULL,
[EmployeeID] [nchar](10) NULL,
[FullName] [char](50) NULL,
[AccountName] [char](20) NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO

  1. Populate the table with some data, you can go to SQL Studio, select Edit the table and add the two records below

FirstName,LastName,EMail,EmployeeID,FullName,AccountName

John,Smith,jsmith@corp.contoso.com,11255,John Smith,jsmith

Jane,Doe,jdoe@contoso.com,11257,Jane Doe,jdoe

Creating the Webservice

  1. Add IIS Role to your Windows 2012 R2 server. My IIS is 8.5. Make sure the following specifications are added, go to Add Roles and features and select

Roles\Security

-Basic Authentication

Roles\Application Development

-Application Initialization

-ASP

-ASP.NET 3.5

-ASP.NET 4.5

Features\.NET Framework 4.5\WCF Services

-Http Activation

  1. Create a directory under your C:\Inetpub\www.root named WebServices
  2. Create a file in the WebServices folder called “DemoContosoDBUpdate.asmx”. Copy this code into the file. Change the Data Source to your SQL server name

<%@ WebService Language=’c#’ Class=”DEMOContosoDBUpdate” %>

using System;

using System.Data;

using System.Data.SqlClient;

using System.Web.Services;

public class DEMOContosoDBUpdate:WebService

{

public SqlConnection connection = new SqlConnection(“Data Source=tlkfim1;Integrated Security=SSPI;Initial Catalog=Contoso”);

[WebMethod]

public void UpdateEmployees(string employeeId, string email)

{

using (SqlCommand cmd = new SqlCommand(“UPDATE HR SET Email = @Email WHERE EmployeeId = @employeeId”))

{

cmd.Parameters.AddWithValue(“@employeeId”, employeeId);

cmd.Parameters.AddWithValue(“@email”, email);

cmd.Connection = connection;

connection.Open();

cmd.ExecuteNonQuery();

connection.Close();

}

}

}

  1. Create a file in C:\Inetpub\wwwroot call web.config. Copy this into the file

<configuration>
<system.web>
<webServices>
<protocols>
<add name=”HttpGet”/>
<add name=”HttpPost”/>
</protocols>
</webServices>
</system.web>
</configuration>

  1. Verify that the Webservice by opening a browser and entering the following address. Change the server name to yours.

http://tlkadconnect1/WebServices/DEMOContosoDBupdate.asmx/UpdateEmployees?employeeid=11257&email=jdoe3@contoso.com

You get a blank screen in your browser

Look in the DB and you will see that that the email address for 11257 is updated.

Creating the ECMA2 Dll file

A couple of differences I want to point out from the GetEmployee code we had before. The GetEmployee functions are still there because we are going to use that. We have added the following

  1. Enabled the IMAExtensible2CallExport
  2. Add the Export functions

Let’s start

  1. In the Synchronization Manager, at the top, select Management Agents.
  2. At the top, select Actions, select Create Extension Projects, and choose Extensible Connectivity 2.0 Extension. This will open a Create Extension Project dialog box.
  3. In the Create Extension Project dialog box, next to Programming Language: select Visual C#
  4. In the Create Extension Project dialog box, next to Visual Studio Version: select Visual Studio 2010.
  5. In the Create Extension Project dialog box, next to Project Name: enter WSC1Update_ECMA2.
  6. Click OK. This will launch Visual Studio 2010.
  7. On the right, at the top, under the Solutions Explorer, double-click WSC1Update_ECMA2. This will open the file with code that has already been written.
  8. Copy and paste this code (overwrite what is there)

using System;

using System.Data;

using System.IO;

using System.Xml;

using System.Text;

using System.Collections.Specialized;

using Microsoft.MetadirectoryServices;

using System.Collections.ObjectModel;

using System.Collections.Generic;

using System.Net;

namespace FimSync_Ezma

{

public class EzmaExtension :

IMAExtensible2CallExport,

IMAExtensible2CallImport,

//IMAExtensible2FileImport,

//IMAExtensible2FileExport,

//IMAExtensible2GetHierarchy,

IMAExtensible2GetSchema,

IMAExtensible2GetCapabilities,

IMAExtensible2GetParameters

//IMAExtensible2GetPartitions

{

private int m_importDefaultPageSize = 1200;

private int m_importMaxPageSize = 1500;

private int m_exportDefaultPageSize = 10;

private int m_exportMaxPageSize = 20;

public string Password;

public string Username;

public string path;

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

//

}

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 int ImportDefaultPageSize

{

get

{

return m_importDefaultPageSize;

}

}

public int ImportMaxPageSize

{

get

{

return m_importMaxPageSize;

}

}

public IList GetConfigParameters(KeyedCollection<string, ConfigParameter>

configParameters, ConfigParameterPage page)

{

List configParametersDefinitions = new List();

switch (page)

{

case ConfigParameterPage.Connectivity:

configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter(“path”, “”));

configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter(“Exportpath”, “”));

configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter(“Username”, “”));

configParametersDefinitions.Add(ConfigParameterDefinition.CreateStringParameter(“Password”, “”));

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;

}

public Schema GetSchema(KeyedCollection<string, ConfigParameter> configParameters)

{

Microsoft.MetadirectoryServices.SchemaType personType = Microsoft.MetadirectoryServices.SchemaType.Create

(“Person”, false);

//myname = configParameters[“myname”].Value;

string myattribute = “Firstname”;

string myattributes = “Email”;

string myattribute2 = “Lastname”;

string myattribute3 = “AccountName”;

string myattribute4 = “EmployeeID”;

string myattribute5 = “Fullname”;

if (myattribute == “Firstname”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute, AttributeType.String));

}

if (myattributes == “Email”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattributes, AttributeType.String));

}

if (myattribute2 == “Lastname”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute2, AttributeType.String));

}

if (myattribute3 == “AccountName”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute3, AttributeType.String));

}

if (myattribute4 == “EmployeeID”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute4, AttributeType.String));

}

if (myattribute5 == “Fullname”)

{

personType.Attributes.Add(SchemaAttribute.CreateAnchorAttribute(myattribute5, AttributeType.String));

}

Schema schema = Schema.Create();

schema.Types.Add(personType);

return schema;

}

public OpenImportConnectionResults OpenImportConnection(KeyedCollection<string, ConfigParameter> configParameters,

Schema types, OpenImportConnectionRunStep importRunStep)

{

this.path = configParameters[“path”].Value;

this.Username = configParameters[“Username”].Value;

this.Password = configParameters[“Password”].Value;

return new OpenImportConnectionResults();

}

public GetImportEntriesResults GetImportEntries(GetImportEntriesRunStep importRunStep)

{

List csentries = new List();

GetImportEntriesResults importReturnInfo;

// Create the web request

HttpWebRequest request = WebRequest.Create(this.path) as HttpWebRequest;

// Get response

using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)

{

//load data into dataset

DataSet da = new DataSet();

da.ReadXml(response.GetResponseStream());

for (int i = 0; i <= da.Tables[“Employees”].Rows.Count – 1; i++)

{

myFirst = da.Tables[“Employees”].Rows[i].ItemArray.GetValue(0).ToString().Trim();

myLast = da.Tables[“Employees”].Rows[i].ItemArray.GetValue(1).ToString().Trim();

myEmail = da.Tables[“Employees”].Rows[i].ItemArray.GetValue(2).ToString().Trim();

myEmpID = da.Tables[“Employees”].Rows[i].ItemArray.GetValue(3).ToString().Trim();

myFull = da.Tables[“Employees”].Rows[i].ItemArray.GetValue(4).ToString().Trim();

myAccount = da.Tables[“Employees”].Rows[i].ItemArray.GetValue(5).ToString().Trim();

CSEntryChange csentry1 = CSEntryChange.Create();

csentry1.ObjectModificationType = ObjectModificationType.Add;

csentry1.ObjectType = “Person”;

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“Firstname”, myFirst));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“Lastname”, myLast));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“Email”, myEmail));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“EmployeeID”, myEmpID));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“Fullname”, myFull));

csentry1.AttributeChanges.Add(AttributeChange.CreateAttributeAdd(“AccountName”, myAccount));

csentries.Add(csentry1);

}

}

importReturnInfo = new GetImportEntriesResults();

importReturnInfo.MoreToImport = false;

importReturnInfo.CSEntries = csentries;

return importReturnInfo;

}

public CloseImportConnectionResults CloseImportConnection(CloseImportConnectionRunStep importRunStep)

{

return new CloseImportConnectionResults();

}

public void OpenExportConnection(KeyedCollection<string, ConfigParameter> configParameters, Schema types,

OpenExportConnectionRunStep exportRunStep)

{

this.path = configParameters[“Exportpath”].Value;

this.Username = configParameters[“Username”].Value;

this.Password = configParameters[“Password”].Value;

}

public PutExportEntriesResults PutExportEntries(IList csentries)

{

string exportwebservicepath;

int i = 0;

foreach (CSEntryChange csentryChange in csentries)

{

myEmpID = csentries[i].DN.ToString();

if (csentryChange.ObjectType == “Person”)

{

#region Update

if (csentryChange.ObjectModificationType == ObjectModificationType.Update)

{

foreach (string attribName in csentryChange.ChangedAttributeNames)

{

//Only do this update for the Email attribute

if (attribName == “Email”)

{

if (csentryChange.AttributeChanges[attribName].ModificationType ==

AttributeModificationType.Add)

{

myEmpID = csentryChange.AnchorAttributes[0].Value.ToString();

string attribValue = csentryChange.AttributeChanges[attribName].ValueChanges

[0].Value.ToString();

//call the webservice to update

exportwebservicepath = this.path + “?employeeid=” + myEmpID + “&email=” + attribValue;

HttpWebRequest request = WebRequest.Create(exportwebservicepath) as HttpWebRequest;

HttpWebResponse response = request.GetResponse() as HttpWebResponse;

}

else if (csentryChange.AttributeChanges[attribName].ModificationType ==

AttributeModificationType.Delete)

{

myEmpID = csentryChange.AnchorAttributes[0].Value.ToString();

//call the webservice to update

exportwebservicepath = this.path + “?employeeid=” + myEmpID + “&email=NULL”;

HttpWebRequest request = WebRequest.Create(exportwebservicepath) as HttpWebRequest;

HttpWebResponse response = request.GetResponse() as HttpWebResponse;

}

else if (csentryChange.AttributeChanges[attribName].ModificationType ==

AttributeModificationType.Replace)

{

myEmpID = csentryChange.AnchorAttributes[0].Value.ToString();

string attribValue = csentryChange.AttributeChanges[attribName].ValueChanges

[0].Value.ToString();

//call the webservice to update

exportwebservicepath = this.path + “?employeeid=” + myEmpID + “&email=” + attribValue;

HttpWebRequest request = WebRequest.Create(exportwebservicepath) as HttpWebRequest;

HttpWebResponse response = request.GetResponse() as HttpWebResponse;

}

else if (csentryChange.AttributeChanges[attribName].ModificationType ==

AttributeModificationType.Update)

{

string attribValue = “”;

foreach (ValueChange valueChange in csentryChange.AttributeChanges

[attribName].ValueChanges)

{

if (valueChange.ModificationType == ValueModificationType.Add)

{

attribValue = valueChange.Value.ToString();

break;

}

}

myEmpID = csentryChange.AnchorAttributes[0].Value.ToString();

//call the webservice to update

exportwebservicepath = this.path + “?employeeid=” + myEmpID + “&email=” + attribValue;

HttpWebRequest request = WebRequest.Create(exportwebservicepath) as HttpWebRequest;

HttpWebResponse response = request.GetResponse() as HttpWebResponse;

}

}

}

}

}

#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;

}

}

};

}

  1. At the top, select Build and choose Build Solution from the drop-down. At the bottom, in the Output window you should see Build: 1 succeeded.

Create the Webservice ECMA2 connector

  1. In the Synchronization Manager, click Management Agents
  2. In the Synchronization Service, at the top, select Management Agents and over on the right, under Actions, select Create. This will open a Create Management Agent wizard.
  3. On the Create Management Agent screen, next to Management Agent for: select Extensible Connectivity 2
  4. On the Create Management Agent screen, next to Name: enter WSC1_ECMA2

  1. Remove the check from Run this management agent in a separate process. This will allow for debugging should the need arise.
  2. Click Next.
  3. On the Select Extension DLL screen, click Browse and select WSC_ECMA2.dll. Click OK.
  4. On the Select Extension DLL screen, click Refresh interfaces. This will populate the box below. It should support Import

  1. Click Next.
  2. On the Connectivity screen, next to path enter: the Webservice url for GetEmployees (see previous blogpost – http://tlkadconnect1/WebServices/DEMOContosoDBupdate.asmx/GetEmployees) and in ExportPath enter the url for the UpdateEmployees (http://tlkadconnect1/WebServices/DEMOContosoDBupdate.asmx/UpdateEmployees) you have already tested
  3. On the Connectivity screen, next to username enter: the id that you tested with
  4. On the Connectivity screen, next to password enter: password

  1. Click Next
  2. On the Configure Partitions and Hierarchies screen, leave the defaults.

  1. Click Next.
  2. On the Select Object Types screen, select Person.

  1. Click Next.
  2. On the Select Attributes screen, select all six.

  1. Click Next.
  2. On the Configure Anchors screen, click Specify Anchor. This will open a Set Anchor dialog box.
  3. On the Set Anchor dialog box, you may see several attributes there, click specify anchor and remove all attributes except employeeid and click Add>. Click OK.

  1. Click Next.
  2. On the Configure Connector Filter screen, click Next.

  1. On the Configure Join and Projection Rules screen, click New Projection Rule. This will open a Projection dialog box.
  2. On the Projection dialog box, verify Declared is selected.
  3. On the Projection dialog box, verify Person is in the box next to Metaverse object type.
  4. Click OK.
  5. On the Configure Join and Projection Rules screen, click New Join Rule. This will open a Join Rule for Person dialog box.
  6. Under Data source attribute select EmployeeID.
  7. Under Metaverse attribute select employeeID.
  8. Click OK. This will bring up a dialog box that states you are attempting to join a non-indexed metaverse attribute. Click OK. Click OK.

  1. Click Next
  2. Configure Attribute flow as shown

  1. Click Next
  2. On the Configure Deprovisioning screen, select “Make them disconnectors”

  1. Click Next
  2. On the Configure Extensions screen click finish.

  1. Configure Full Import and Export run profile

  1. Change the email in AD in AD and make sure that that change is imported into the MV via the AD MA connector. Run the Import on the ECMA2, it should be successful. Then run the sync, this will put an export item in the queue to the ECMA2. Then run the Export