Rule Extensions in Microsoft Identity Manager 2016 Synchronization Service provide the ability to implement custom business logic using .NET code. This guide covers the development, deployment, and best practices for creating robust rule extensions that extend the synchronization capabilities beyond the built-in functionality.
Overview
Rule Extensions allow developers to:
- Implement complex attribute transformations
- Perform external system integrations
- Execute custom validation logic
- Handle specialized business requirements
- Integrate with third-party APIs and services
Development Environment Setup
Prerequisites
- Visual Studio (2019 or later recommended)
- .NET Framework 4.5.2 or higher
- MIM 2016 Synchronization Service installed
- Microsoft.MetadirectoryServices assemblies
Project Configuration
Create a Class Library Project
// Target Framework: .NET Framework 4.5.2 or higher // Platform: x64 (to match MIM Sync Service)
Add Required References
Microsoft.MetadirectoryServices.dll System.DirectoryServices.dll (if needed) System.Data.dll (for database operations)
Configure Build Output
Output Path: C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\
Rule Extension Types
1. Attribute Flow Rules
Control how attributes flow between connector spaces and the metaverse.
using Microsoft.MetadirectoryServices;
public class AttributeFlowExtensions : IMAExtensible2CallIn
{
public void MapAttributesForImport(
string FlowRuleName,
CSEntry csentry,
MVEntry mventry)
{
switch (FlowRuleName)
{
case "FormatDisplayName":
FormatDisplayName(csentry, mventry);
break;
case "GenerateEmployeeID":
GenerateEmployeeID(csentry, mventry);
break;
}
}
private void FormatDisplayName(CSEntry csentry, MVEntry mventry)
{
string firstName = csentry["givenName"].StringValue;
string lastName = csentry["sn"].StringValue;
if (!string.IsNullOrEmpty(firstName) && !string.IsNullOrEmpty(lastName))
{
mventry["displayName"].StringValue = $"{lastName}, {firstName}";
}
}
private void GenerateEmployeeID(CSEntry csentry, MVEntry mventry)
{
// Custom logic to generate employee ID
string department = csentry["department"].StringValue;
string lastName = csentry["sn"].StringValue;
if (!string.IsNullOrEmpty(department) && !string.IsNullOrEmpty(lastName))
{
string employeeID = $"{department.Substring(0, 3).ToUpper()}{lastName.Substring(0, 4).ToUpper()}{DateTime.Now.Year}";
mventry["employeeID"].StringValue = employeeID;
}
}
}
2. Object Processing Rules
Handle object-level operations during import and export.
public void MapAttributesForExport(
string FlowRuleName,
MVEntry mventry,
CSEntry csentry)
{
switch (FlowRuleName)
{
case "SetAccountStatus":
SetAccountStatus(mventry, csentry);
break;
case "ConfigureGroupMembership":
ConfigureGroupMembership(mventry, csentry);
break;
}
}
private void SetAccountStatus(MVEntry mventry, CSEntry csentry)
{
string employeeStatus = mventry["employeeStatus"].StringValue;
switch (employeeStatus?.ToLower())
{
case "active":
csentry["userAccountControl"].IntegerValue = 512; // Normal account
break;
case "disabled":
csentry["userAccountControl"].IntegerValue = 514; // Disabled account
break;
case "terminated":
// Don't export - let deprovisioning rule handle
break;
}
}
3. Provisioning Rules
Control object provisioning and deprovisioning logic.
public DeprovisionAction ShouldDeleteFromMV(
CSEntry csentry,
MVEntry mventry)
{
// Don't delete if other connectors have the object
if (mventry.ConnectedMAs.Count > 1)
{
return DeprovisionAction.ExplicitDisconnector;
}
// Check if object should be preserved
string objectType = mventry["objectType"].StringValue;
if (objectType == "criticalAccount")
{
return DeprovisionAction.ExplicitDisconnector;
}
return DeprovisionAction.Delete;
}
public bool ShouldProjectToMV(CSEntry csentry, out string MVObjectType)
{
MVObjectType = "";
// Only project if required attributes are present
if (csentry["employeeID"].IsPresent &&
csentry["mail"].IsPresent)
{
MVObjectType = "person";
return true;
}
return false;
}
Advanced Scenarios
1. External API Integration
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
public class ExternalAPIIntegration
{
private static readonly HttpClient httpClient = new HttpClient();
public async Task<string> GetUserDataFromAPI(string employeeId)
{
try
{
string apiUrl = $"https://api.company.com/users/{employeeId}";
var response = await httpClient.GetAsync(apiUrl);
response.EnsureSuccessStatusCode();
string jsonResponse = await response.Content.ReadAsStringAsync();
dynamic userData = JsonConvert.DeserializeObject(jsonResponse);
return userData.department;
}
catch (Exception ex)
{
// Log error and handle gracefully
WriteLog($"API call failed: {ex.Message}");
return null;
}
}
}
2. Database Lookups
using System.Data.SqlClient;
public class DatabaseLookup
{
private string connectionString = "Server=dbserver;Database=HR;Integrated Security=true;";
public string LookupManagerInfo(string employeeId)
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
string query = @"
SELECT m.EmployeeID
FROM Employees e
INNER JOIN Employees m ON e.ManagerID = m.EmployeeID
WHERE e.EmployeeID = @EmployeeID";
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@EmployeeID", employeeId);
object result = command.ExecuteScalar();
return result?.ToString();
}
}
}
}
3. Complex Attribute Transformations
public void TransformPhoneNumber(CSEntry csentry, MVEntry mventry)
{
string rawPhone = csentry["telephoneNumber"].StringValue;
if (!string.IsNullOrEmpty(rawPhone))
{
// Remove all non-numeric characters
string digitsOnly = new string(rawPhone.Where(char.IsDigit).ToArray());
// Format based on length
switch (digitsOnly.Length)
{
case 7:
mventry["telephoneNumber"].StringValue = $"{digitsOnly.Substring(0, 3)}-{digitsOnly.Substring(3)}";
break;
case 10:
mventry["telephoneNumber"].StringValue = $"({digitsOnly.Substring(0, 3)}) {digitsOnly.Substring(3, 3)}-{digitsOnly.Substring(6)}";
break;
case 11 when digitsOnly.StartsWith("1"):
string domestic = digitsOnly.Substring(1);
mventry["telephoneNumber"].StringValue = $"+1 ({domestic.Substring(0, 3)}) {domestic.Substring(3, 3)}-{domestic.Substring(6)}";
break;
default:
mventry["telephoneNumber"].StringValue = rawPhone; // Keep original if can't format
break;
}
}
}
Error Handling and Logging
1. Exception Management
public void MapAttributesForImport(
string FlowRuleName,
CSEntry csentry,
MVEntry mventry)
{
try
{
switch (FlowRuleName)
{
case "ComplexTransformation":
ComplexTransformation(csentry, mventry);
break;
}
}
catch (UnexpectedDataException ex)
{
// Log specific data issues
WriteLog($"Data validation failed for {csentry.DN}: {ex.Message}");
throw new EntryPointNotImplementedException($"Data validation error: {ex.Message}");
}
catch (Exception ex)
{
// Log general errors
WriteLog($"Unexpected error in {FlowRuleName}: {ex.Message}");
throw;
}
}
2. Logging Implementation
using System.IO;
public static class Logger
{
private static readonly object lockObject = new object();
private static readonly string logPath = @"C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Logs\RuleExtensions.log";
public static void WriteLog(string message)
{
lock (lockObject)
{
try
{
string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}";
File.AppendAllText(logPath, logEntry + Environment.NewLine);
}
catch
{
// Fail silently - don't break sync for logging issues
}
}
}
}
Testing and Debugging
1. Unit Testing Framework
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class RuleExtensionTests
{
[TestMethod]
public void TestDisplayNameFormatting()
{
// Arrange
var extension = new AttributeFlowExtensions();
var mockCSEntry = CreateMockCSEntry();
var mockMVEntry = CreateMockMVEntry();
mockCSEntry["givenName"].StringValue = "John";
mockCSEntry["sn"].StringValue = "Doe";
// Act
extension.MapAttributesForImport("FormatDisplayName", mockCSEntry, mockMVEntry);
// Assert
Assert.AreEqual("Doe, John", mockMVEntry["displayName"].StringValue);
}
}
2. Debug Configuration
#if DEBUG
public static void WriteDebugLog(string message)
{
string debugLogPath = @"C:\Temp\MIMDebug.log";
File.AppendAllText(debugLogPath, $"{DateTime.Now:HH:mm:ss} - {message}\n");
}
#endif
Deployment and Configuration
1. Assembly Deployment
Build the Project in Release mode
Copy the Assembly to Extensions folder:
C:\Program Files\Microsoft Forefront Identity Manager\2010\Synchronization Service\Extensions\
Restart the Synchronization Service
Configure Rules in Synchronization Service Manager
2. Rule Configuration
- Open Synchronization Service Manager
- Navigate to Management Agents
- Configure Attribute Flow Rules
- Specify Rule Extension Method Names
- Test with Preview
3. Version Management
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
Performance Considerations
1. Efficient Code Patterns
// Use static variables for expensive operations
private static readonly Regex phoneRegex = new Regex(@"[^\d]", RegexOptions.Compiled);
// Cache frequently accessed data
private static readonly Dictionary<string, string> departmentCache = new Dictionary<string, string>();
public void OptimizedTransformation(CSEntry csentry, MVEntry mventry)
{
// Minimize object creation
string employeeId = csentry["employeeID"].StringValue;
if (string.IsNullOrEmpty(employeeId))
return;
// Use cached data when possible
if (departmentCache.TryGetValue(employeeId, out string cachedDepartment))
{
mventry["department"].StringValue = cachedDepartment;
return;
}
// Fetch and cache if not found
string department = LookupDepartment(employeeId);
departmentCache[employeeId] = department;
mventry["department"].StringValue = department;
}
2. Resource Management
public void ManageResources()
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
// Database operations
} // Connection automatically disposed
}
Security Best Practices
1. Secure Configuration
public class SecureConfiguration
{
private static string GetEncryptedConnectionString()
{
// Use Windows DPAPI or similar for sensitive data
string encryptedValue = ConfigurationManager.AppSettings["EncryptedConnectionString"];
return DecryptString(encryptedValue);
}
}
2. Input Validation
public void ValidateInput(CSEntry csentry)
{
string email = csentry["mail"].StringValue;
if (!string.IsNullOrEmpty(email))
{
if (!IsValidEmail(email))
{
throw new InvalidDataException($"Invalid email format: {email}");
}
}
}
private bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
Troubleshooting Common Issues
1. Assembly Loading Issues
Problem: Rule extension not found Solution:
- Verify assembly is in correct folder
- Check assembly dependencies
- Restart synchronization service
2. Performance Problems
Problem: Slow synchronization Solution:
- Profile rule extension code
- Optimize database queries
- Implement caching strategies
3. Memory Leaks
Problem: Increasing memory usage Solution:
- Dispose resources properly
- Avoid static collections that grow indefinitely
- Monitor object lifecycle
Best Practices
1. Design Principles
- Keep it Simple: Implement only necessary complexity
- Fail Fast: Validate inputs early
- Be Defensive: Handle unexpected scenarios
- Log Appropriately: Balance detail with performance
2. Code Organization
- Separate Concerns: One class per responsibility
- Use Interfaces: Enable testability
- Document Code: Include XML documentation
- Version Control: Track all changes
3. Testing Strategy
- Unit Tests: Test individual methods
- Integration Tests: Test with MIM components
- Performance Tests: Validate under load
- Regression Tests: Prevent breaking changes
Conclusion
Rule Extensions provide powerful capabilities for customizing MIM 2016 Synchronization Service behavior. By following the patterns, practices, and guidelines outlined in this document, developers can create robust, maintainable, and performant extensions that meet complex business requirements while maintaining system stability and security.
Related Topics
- MIM 2016 Synchronization Service Overview: Main service architecture
- SQL Synchronization Guide: Implementation examples
- Troubleshooting Guide: Common issues and solutions
- Performance Tuning: Optimization strategies