FIM 2010: Configuration Deployment (Part 1: Schema)

My main customer has an environment with 3 stages (Development, Test, Production), so very perfect to work with. But deployment from one stage to the other is not very neat with the default tools that are shipped with Forefront Identity Manager 2010, as (you all know it) there are only a few PowerShell scripts you deal with.

I’ve searched a lot of blogs, also TechNet forum and wiki, but it seems nobody has ever wrote an article about this part of work on a FIM solution (or possibly I’m unable to find 😉 ).

So for the schema deployment you have to export schema with PowerShell on both (source and target) stages, then copy the file together and use another PowerShell script to join them and calculate the delta. Finally import the delta with another script.

As I have to deal with a multi-language environment, the –AllLocales parameter slows up the things, in addition I want to get rid of jumping between the RDP sessions to do all this.

So I modified the default scripts and put them all together in one and use PowerShell jobs to get the export from both environments in parallel (using jobs can be a little bit tricky). After that the delta is calculated and you are asked if you want to import the changes into FIM.

So here is the script I use to deploy my schema changes from one environment to the other:
(This script is currently desinged to run from the target system)

# use -AllLocales parameter to deploy schema data incl. localizations

#### Configuration Section ####

$undoneFile = "D:\Deploy\undone.xml"

#### Cleanup old data files ####
remove-item $sourceFilename -ErrorAction:SilentlyContinue
remove-item $destFilename -ErrorAction:SilentlyContinue
remove-item $deployFilename -ErrorAction:SilentlyContinue
remove-item $undoneFile -ErrorAction:SilentlyContinue

#### Define Functions ####
function CommitPortalChanges($deployFile)
	$imports = ConvertTo-FIMResource -file $deployFile
	if($imports -eq $null)
		throw (new-object NullReferenceException -ArgumentList "Changes is null.  Check that the changes file has data.")
	Write-Host "Importing changes into T&A environment"
	$undoneImports = $imports | Import-FIMConfig
	if($undoneImports -eq $null)
		Write-Host "Import complete."
		Write-Host "There were " $undoneImports.Count " uncompleted imports."
		$undoneImports | ConvertFrom-FIMResource -file $undoneFile
		Write-Host "Please see the documentation on how to resolve the issues."

function SyncSchema($sourceFile, $destFile, $deployFile)
	$joinrules = @{
		# === Schema configuration ===
		# This is based on the system names of attributes and objects
		# Notice that BindingDescription is joined using its reference attributes.
		ObjectTypeDescription = "Name";
		AttributeTypeDescription = "Name";
		BindingDescription = "BoundObjectType BoundAttributeType";

	if(@(get-pssnapin | where-object {$_.Name -eq "FIMAutomation"} ).count -eq 0) {add-pssnapin FIMAutomation}

	$destination = ConvertTo-FIMResource -file $destFile
	if($destination -eq $null)
		{ throw (new-object NullReferenceException -ArgumentList "Destination Schema is null.  Check that the destination file has data.") }

	Write-Host "Loaded destination file: " $destFile " with "  $destination.Count " objects."

	$source = ConvertTo-FIMResource -file $sourceFile
	if($source -eq $null)
		{ throw (new-object NullReferenceException -ArgumentList "Source Schema is null.  Check that the source file has data.") }

	Write-Host "Loaded source file: " $sourceFile " with " $source.Count " objects."
	Write-Host "Executing join between source and destination."
	$matches = Join-FIMConfig -source $source -target $destination -join $joinrules -defaultJoin DisplayName
	if($matches -eq $null)
		{ throw (new-object NullReferenceException -ArgumentList "Matches is null.  Check that the join succeeded and join criteria is correct for your environment.") }
	Write-Host "Executing compare between matched objects in source and destination."
	$changes = $matches | Compare-FIMConfig
	if($changes -eq $null)
		{ throw (new-object NullReferenceException -ArgumentList "Changes is null.  Check that no errors occurred while generating changes.") }
	Write-Host "Identified " $changes.Count " changes to apply to destination."
	Write-Host "Saving changes to " $deployFile "."
	$changes | ConvertFrom-FIMResource -file $deployFile
	Write-Host "Sync complete. The next step is to commit the changes using CommitChanges.ps1."

$functions = {
	function GetSchema($filename, $serverFQDN, $creds, $AllLocales)
		if(@(get-pssnapin | where-object {$_.Name -eq "FIMAutomation"} ).count -eq 0) {add-pssnapin FIMAutomation}
		$uri="http://" + $serverFQDN + ":5725/ResourceManagementService"
		if ($AllLocales -eq $true)
			{ $schema = Export-FIMConfig -uri $uri -credential $creds -allLocales -schemaConfig -customConfig "/SynchronizationFilter" }
			{ $schema = Export-FIMConfig -uri $uri -credential $creds -schemaConfig -customConfig "/SynchronizationFilter" }
		$schema | ConvertFrom-FIMResource -file $filename

#### Main Script ####
$creds=Get-Credential -message "Enter credentials for source FIM"
$myargs=@($sourceFileName, $sourceServer, $creds, $AllLocales.IsPresent)
start-job -name "SourceFIM" -init $functions -script { GetSchema $args[0] $args[1] $args[2] $args[3] } -ArgumentList $myargs

$creds=Get-Credential -message "Enter credentials for destination FIM"
$myargs=@($destFileName, $destServer, $creds, $AllLocales.IsPresent)
start-job -name "DestFIM" -init $functions -script { GetSchema $args[0] $args[1] $args[2] $args[3] } -ArgumentList $myargs

Write-Host "Waiting for Schema Export to complete..."
get-job | wait-job

Write-Host "Exports complete: Starting Schema compare..."
SyncSchema $sourceFilename $destFilename $deployFilename

$input=Read-Host "Do you want commit changes to destination FIM ? (y/n)"
if ($input -eq "y")
	CommitPortalChanges $deployFilename

Due to the implementation of PowerShell jobs, if you get any error or exceptions you can retrieve the output from both background jobs with the following command:

Receive-Job <JobNumber>


Receive-Job –name <JobName>

The script also creates the undone.xml file like the original join script, so you can use the original ResumeUndoneImport.ps1 script to retry importing the unprocessed changes.

So this was the easiest part, next time I will take a look on the deployment of the policies (like Sets, MPRs and Workflows) in which I have to deal with possible different values in the stages (like for e.g. NetBIOS Domain Names or Part of the DN) and also some objects for which I don’t want to have changes deployed, as they have to be different within these stages.

So, come back in a few days or use the RSS feed to don’t miss Part 2: Policy Deployment.

Speed up FIM 2010 R2 SQL performance by rebuild/reorganize indexes

Recently a user in the Technet Forum had performance issues with SQL MA’s on Syncs, see this Thread. This can happen over time mostly in large environments or environments many changes.

The problem was fragmentation of indexes, and the solution was to rebuild/reorganize these indexes as documented in the Deployment Guide for FIM.

I followed the best practices in mostly all of my projects, also for the SQL Server deployment, but don’t have in mind to check the indexes regularly or on performance issues. So keep that in mind (as I do now) that maybe the Reorganize or Rebuild of SQL indexes can solve your problem.

How-To: Manage Group Membership from the User UI in FIM 2010 R2

So, let’s start this blog with an Technet Wiki Articel i wrote a couple of days ago. This is the solution from one of my customers, which need to have helpdesk users to manage group membership like it is done in ADUC.

One of the missing features in FIM Portal is that you can not OOB change the group membership of users in the user UI, like it is done in AD with the memberOf. But with the Powershell Activity an some small scripts and RCDC editing you can build a really cool solution for that.

Technet Wiki: FIM 2010 R2 HowTo Manage Group Membership from User UI