Thursday, December 19, 2013

FIM 2010 R2 SSPR: Cannot access password registration portal - Error 3000

In a development environment, after upgrading to FIM 2010 R2, I configured (as you would) the new URLs and settings for the SSPR password registration and reset pages.  However, much to my surprise (or not), the registration site did not work as in another testing environment.  When prompted to click Next in order to begin the registration process, the portal would immediately throw the following error:


Looking at the administrative event logs, the following stream of events were occurring during the portal being accessed and after clicking Next:


The three errors state:

HttpContext.Current.User.Identity.Name is Null or Empty

Microsoft.IdentityManagement.CredentialManagement.Portal: System.Web.HttpUnhandledException: ScriptManager_AsyncPostBackError ---> System.InvalidOperationException: HttpContext.Current.User.Identity.Name is Null or Empty

at Microsoft.IdentityManagement.CredentialManagement.Portal.Components.RegistrationDriver.GetDomainAndUserName(String& domain, String& userName)

at Microsoft.IdentityManagement.CredentialManagement.Portal.Components.RegistrationDriver.InitiateRegistration()

at Microsoft.IdentityManagement.CredentialManagement.Portal.Registration.Next()

at System.Web.UI.WebControls.Button.OnClick(EventArgs e)

at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)

at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)

at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

--- End of inner exception stack trace ---

at Microsoft.IdentityManagement.CredentialManagement.Portal.Site.ScriptManager_AsyncPostBackError(Object sender, AsyncPostBackErrorEventArgs eventArgs)

at System.Web.UI.ScriptManager.OnAsyncPostBackError(AsyncPostBackErrorEventArgs e)

at System.Web.UI.PageRequestManager.OnPageError(Object sender, EventArgs e)

at System.Web.UI.TemplateControl.OnError(EventArgs e)

at System.Web.UI.Page.HandleError(Exception e)

at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

at System.Web.UI.Page.ProcessRequest()

at System.Web.UI.Page.ProcessRequest(HttpContext context)

at ASP.default_aspx.ProcessRequest(HttpContext context)

at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()

at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)


The error page was displayed to the user.

Details:

Title: Error

Message: An error has occurred. Please try again, and if the problem persists, contact your help desk or system administrator. (Error 3000)

Source:

Attributes:

Details: System.InvalidOperationException: HttpContext.Current.User.Identity.Name is Null or Empty

at Microsoft.IdentityManagement.CredentialManagement.Portal.Components.RegistrationDriver.GetDomainAndUserName(String& domain, String& userName)

at Microsoft.IdentityManagement.CredentialManagement.Portal.Components.RegistrationDriver.InitiateRegistration()

at Microsoft.IdentityManagement.CredentialManagement.Portal.Registration.Next()

at System.Web.UI.WebControls.Button.OnClick(EventArgs e)

at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)

at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)

at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

CorrelationId:

RequestId:

ErrorCode: 3000

CaughtTime: 12/19/2013 15:53:14

Web Portal: FIM Password Registration Portal
 


After doing some research, I found the following very insightful articles by Tim Macaulay (Microsoft):


Interestingly, the second article discussed disabling Kernel Mode Authentication, and yet, in the following article, it states per the SPN registration portal requirements:

The SSPR portals use IIS 7.0/7.5. IIS 7.0/7.5 has an authentication feature - 'Enable Kernel Mode Authentication'. With this feature the Kerberos ticket for the requested service is decrypted using Machine account (Local system) of the IIS server. It no longer depends upon the application pool Identity for this purpose. The following assumes that the password registration and reset portals are being accessed through a custom host header. In this instance the SPN is required only for the IIS machine account and not for our FIM Password Service account.

Therefore, having registered the machine name (DOMAIN\computername$) using the "setspn" command, as well as other configurations, I knew that Kerberos authentication had been set up properly, so WHAT WAS GOING ON!!

All this, and I noticed that Windows Authentication, after running setup, was disabled, with Anonymous authentication enabled.  These IIS settings may have been edited in the past, and were likely retained during the upgrade.  Also interesting is the fact that these are the settings for the password reset portal, which is correct.

Make sure the following configurations are set in IIS for the Password Registration portal:

 

Hope this helps!

-jose the admin

Preparing for Microsoft MCTS exam 70-158: Forefront Identity Manager 2010, Configuring

After working with FIM 2010 now for almost two years in a very "thrown to the wolves" fashion, I have come a long way.  I now feel it is time to certify my pain by taking exam 70-158.

As anyone that has attempted this before will know by now, preparing for this is a challenge.  First off, we are not talking about any flagship product, such as Windows Server, Exchange, SharePoint, Lync, etc.  Therefore, there is not a lot in the way of literature, examples, or coverage.

However, during my journey in learning about and at the same time managing and maintaining FIM, as well as architecting solutions for my organization, I have come across a wealth of knowledge that would presumably help anyone trying to meet the exam objectives.

First, let me talk literature.

I must say that the book by David Lundell and Brad Turner, FIM R2 Best Practices Volume 1: Introduction, Architecture And Installation Of Forefront Identity Manager 2010 R2 is a fantastic resource.  I have swallowed this book whole, and find that my understanding of WHY something should be done a certain way as regards FIM is largely guided by the directions of this book.  It is a joy to read, concise considering the material, and yet sheds light on topics seldom found in TechNet or in forums.  I found the most benefits in the topics surrounding capacity planning, general architecture planning, and service account security implementation.  I highly recommend going through the entire book as it will give you very solid foundation on the architecture, installation, and initial configurations of FIM and its client.



In addition, many best practices and overall discussion can be found in the book Microsoft Forefront Identity Manager 2010 R2 Handbook by Kent Nordstrom.  I found many interesting points, including some consultant suggestions during configuring FIM, that helped understand FIM in a deeper way.  I especially liked the chapters on the seldom discussed topics around certificate management and smart cards.

 
Obviously, there is TechNet, which does help:
 
 
However, I found the following community supporter sites to be critical in getting more real-world knowledge on FIM:
 
 
There may be others, but those are the ones I frequent most.  Of special mention is the user group, originated and supported by The FIM Team (Bob Bradley, Carol Wapshere, and the like).  Their leadership and pioneering in IdM has been inspirational as well as extremely insightful.  Anyone serious about FIM should already know who they are.
 
Finally, there are events and classes held by the Oxford Computer Group and BrightTALK held very often that assist in general understanding of IAM current topics like attestation, BHOLD, and governance.
 
 
Of course, taking the initial foundation course 50382: Implementing Forefront Identity Manager 2010 is highly advisable as a starter:  http://www.microsoft.com/learning/en-us/course.aspx?id=50382b
 
All in all, I continue to love/hate this paradoxical journey towards mastering FIM 2010 and its related solutions.  I feel like the above would definitely assist any who would attempt exam 70-158, or who would just like to get a firm grip on IAM on FIM 2010 in general.
 
Hope this helps!
 
-jose the admin

Monday, May 20, 2013

Record retention and account name building strategies with FIM 2010

I work for an organization which must comply with certain industry standards and business policies that affect the governance of the management and maintenance of IT operations.  As a result, the following become hot topics:

•Data retention
•Email retention
•Identity retention
•Complex account name requirements
Today, I will focus strictly on identity retention and account name requirements.  Although I will be demonstrating within the confines of my organization's requirements, I am sure that one could apply or modify the solution to meet their needs.
REQUIREMENTS: An identity must be retained indefinitely given the various lifecycle changes of that identity.  This could include, but is not limited to, contractors, contingent workers, permanent employees, vendor, generic, and service accounts.  Scenarios might include contractor to perm, perm to contractor, partnered organization identities and federation, termination and rehire, and the like.
We also must keep our provisioning and deprovisioning rules simple, complex configurations at a minimum, and our existing live data clean for daily operations.

Finally, we define that an account name will be the sum of the first initial, middle initial, and last name, whose sum is only up to eight characters (due to legacy system compliance within the organization), and, when a middle name is not present, is substituted with an "x".  When a potential account name conflicts with an existing identity, it is to be appended with a number, starting with one, and increasing as required, while still meeting the eight character maximum limitation.

ASSUMPTIONS: For the sake of this solution, we will assume the following constants:

•All handling of when an employee is provisioned or marked for deprovisioning, as well as transference from employee to contractor, or vice versa, is handled by a combination of object state handling in the provisioning code and advanced join/projection rules.
•If an account is deleted from the FIM portal or Active Directory, then the object deletion rules state to remove all other associated connectors, effectively deleting the metaverse object.

•If an account has an account name and exists in the metaverse, it is to be projected into a SQL database for retention purposes.

•The SQL database (or table therein) is the retention store for all valid user/person objects, with any and all pertinent attribute data associated with those objects.
•If an account is marked as a potential conflict, it is deemed such in the description field of the object, and, by means of workflow handling and notifications, handled manually by personnel.

SOLUTION: The following solution codes what is required to meet the above stated requirements.  As objects will delete from within FIM, it is necessary to move away from the Utils.FindMVEntries() function as a means to check for an existing account.  Instead, we move to check accounts against the SQL table, which maintains all identity data for retentive purposes.
 
 Imports System  
 Imports Microsoft.MetadirectoryServices  
 Imports System.Data  
 Imports System.Data.SqlClient  
 Public Class MVProvision  
   Implements IMVSynchronization  
   Private Const MAX_ACCOUNT_PROVISION_TRIES As Integer = 1000  
   Private Const MAX_ACCOUNT_NAME_LENGTH As Integer = 8  
   Public Sub Provision( _  
     ByVal mventry As MVEntry) _  
     Implements IMVSynchronization.Provision  
     Dim csentry As CSEntry  
     Dim lastname As String  
     Dim displayName As String  
     Dim accountName As String  
     Dim accountXName As String  
     Dim conflictFound As String = Nothing  
     Dim provisionAD As Boolean = True  
     If mventry.ObjectType.Equals("person") Then  
       '----------------------------------------------------------------  
       'User Provisioning  
       '----------------------------------------------------------------  
       If provisionAD Then  
         'First, if the accountname isn't populated we have to calculate it.  
         If mventry.Item("accountName").IsPresent Then  
           accountName = mventry.Item("accountName").Value  
           displayName = mventry.Item("displayName").Value  
         Else  
           'Edit out any spaces and special characters, then concatenate to 6 characters or less  
           If mventry.Item("lastName").IsPresent Then  
             lastname = mventry.Item("lastName").Value  
           Else  
             lastname = ""  
           End If  
           lastname = lastname.Replace(" ", "") 'Remove spaces  
           lastname = lastname.Replace("'", "") 'Remove '  
           lastname = lastname.Replace("-", "") 'Remove -  
           lastname = lastname.Replace("_", "") 'Remove _  
           lastname = lastname.Replace(".", "") 'Remove .  
           lastname = lastname.Replace("&", "") 'Remove &  
           lastname = lastname.Replace("$", "") 'Remove $  
           lastname = lastname.Replace("@", "") 'Remove @  
           accountXName = Me._buildXAccountName(mventry.Item("firstName").Value, lastname)  
           'Check for possible conflict with "X" identity  
           Dim conflictFoundWithX As Boolean = Me._checkAccountWithXExists(accountXName, mventry.Item("firstName").Value, mventry.Item("lastName").Value)  
           If conflictFoundWithX Then  
             conflictFound = "Possible Username Conflict with " + accountXName  
           End If  
           'Loop until a valid account is found or you reach the max number of tries.  
           For i As Integer = 0 To MAX_ACCOUNT_PROVISION_TRIES  
             'Build the Account Name  
             accountName = Me._buildNextAccountName(firstinitial, middleinitial, lastname, i)  
             'Validate that the account is unique  
             If Me._checkIsAccountUnique(accountName) Then  
               'Success! This should be a unique account unless somebody uses it right now before we do  
               'Simply exit the loop and the unique account name will persist  
               Exit For  
             Else  
               'Identify that there may be a potential conflict  
               Dim conflictFoundWithAccountName As Boolean = Me._checkIsAccountConflicting(accountName, mventry.Item("firstName").Value, mventry.Item("lastName").Value)  
               If conflictFoundWithAccountName Then  
                 conflictFound = "Possible Username Conflict with " + accountName  
               End If  
             End If  
             If i >= MAX_ACCOUNT_PROVISION_TRIES Then  
               'Tried to many times.  
               Throw New ApplicationException("Error: Unable to find a unique account name. The maximum number of tries has been reached.")  
             End If  
           Next  
           If Not conflictFound = Nothing Then  
             csentry.Item("Description").Value = conflictFound  
           End If  
         End If  
         successful = True  
         Exit Sub  
       End If  
     End If  
   End Sub  
 #Region "Helper Routines"  
   Private Function _buildNextAccountName(ByVal FirstName As String, ByVal MiddleName As String, ByVal LastName As String, Iteration As Integer) As String  
     Dim rtn As String  
     'Handle MiddleName in case we need to insert "X"  
     If MiddleName IsNot Nothing Then  
       If MiddleName.Substring(0, 1) = " " Then  
         MiddleName = "x"  
       Else  
         MiddleName = MiddleName.Substring(0, 1).ToLower  
       End If  
     Else  
       MiddleName = "x"  
     End If  
     'Build base user name  
     Dim baseUserName As String = FirstName.Substring(0, 1).ToLower() + MiddleName.Substring(0, 1).ToLower() + LastName.ToLower()  
     'Username must be less the MAX_ACCOUNT_NAME_LENGTH number of charachters  
     If baseUserName.Length > MAX_ACCOUNT_NAME_LENGTH Then  
       'Trim it down to MAX_ACCOUNT_NAME_LENGTH  
       baseUserName = baseUserName.Substring(0, MAX_ACCOUNT_NAME_LENGTH)  
     End If  
     If Iteration = 0 Then  
       'This is the first attempt  
       rtn = baseUserName  
     Else  
       'Calculate how to shift the chars over.  
       Dim shiftOffset As Integer = Math.Max(0, baseUserName.Length + Iteration.ToString().Length - MAX_ACCOUNT_NAME_LENGTH)  
       'Append a number to the end of the username  
       rtn = baseUserName.Substring(0, baseUserName.Length - shiftOffset) + Iteration.ToString()  
     End If  
     Return rtn  
   End Function  
   Private Function _buildXAccountName(ByVal FirstName As String, ByVal LastName As String) As String  
     Dim rtn As String  
     Dim XUserName As String = FirstName.Substring(0, 1).ToLower() + "x" + LastName.ToLower()  
     'Username must be less the MAX_ACCOUNT_NAME_LENGTH number of charachters  
     If XUserName.Length > MAX_ACCOUNT_NAME_LENGTH Then  
       'Trim it down to MAX_ACCOUNT_NAME_LENGTH  
       XUserName = XUserName.Substring(0, MAX_ACCOUNT_NAME_LENGTH)  
     End If  
     'Return username with "X" as the middle initial  
     rtn = XUserName  
     Return rtn  
   End Function  
   Private Function _checkAccountWithXExists(ByVal AccountXName As String, ByVal FirstName As String, ByVal LastName As String) As Boolean  
     'Setup the return object  
     Dim rtn As Boolean = False  
     Dim dt As New DataTable  
     'Set up the sql command string  
     Dim strCommand As String = "SELECT TOP 1 firstName,lastName FROM TABLENAME WHERE userName = '" + AccountXName + "';"  
     'Get the connection string  
     Dim ConnString As String = "Data Source=SERVERNAME\INSTANCE;Database=DATABASE;Trusted_Connection=yes"  
     Try  
       'Setup the connection  
       Using conn As New System.Data.SqlClient.SqlConnection(ConnString)  
         Using cmd As New System.Data.SqlClient.SqlCommand(strCommand, conn)  
           Try  
             'Open Connection  
             conn.Open()  
             cmd.CommandTimeout = 0  
             Dim da As New SqlDataAdapter(cmd)  
             'Execute the command  
             da.Fill(dt)  
           Catch ex As Exception  
             Throw ex  
           Finally  
             'Close connection  
             conn.Close()  
           End Try  
         End Using  
       End Using  
     Catch ex As Exception  
       'Error occured  
       Throw ex  
     End Try  
     If dt IsNot Nothing AndAlso dt.Rows.Count > 0 Then  
       'Possible conflict found, check first and last name to see if it indeed could be a conflict.  
       If dt.Rows(0)("lastName") IsNot Nothing AndAlso Not IsDBNull(dt.Rows(0)("lastName")) _  
         AndAlso dt.Rows(0)("lastName").ToString().ToLower = LastName.ToLower() Then  
         If dt.Rows(0)("firstName") IsNot Nothing AndAlso Not IsDBNull(dt.Rows(0)("firstName")) _  
           AndAlso dt.Rows(0)("firstName").ToString().ToLower = FirstName.ToLower() Then  
           'If first and last name exists and are the same as the target identity, then this is possibly a conflicting account.  
           rtn = True  
         End If  
       Else  
         'If things did not check out with first and last name, then there is not a conflict.  
         rtn = False  
       End If  
     Else  
       'No conflicting previous account with an "X" found.  
       rtn = False  
     End If  
     Return rtn  
   End Function  
   Private Function _checkIsAccountUnique(ByVal AccountName As String) As Boolean  
     'Setup the return object  
     Dim rtn As Boolean = False  
     Dim dt As New DataTable  
     'Set up the sql command string  
     Dim strCommand As String = "SELECT TOP 1 firstName,lastName FROM TABLENAME WHERE userName = '" + AccountName + "';"  
     'Get the connection string  
     Dim ConnString As String = "Data Source=SERVERNAME\INSTANCE;Database=DATABASE;Trusted_Connection=yes"  
     Try  
       'Setup the connection  
       Using conn As New System.Data.SqlClient.SqlConnection(ConnString)  
         Using cmd As New System.Data.SqlClient.SqlCommand(strCommand, conn)  
           Try  
             'Open Connection  
             conn.Open()  
             cmd.CommandTimeout = 0  
             Dim da As New SqlDataAdapter(cmd)  
             'Execute the command  
             da.Fill(dt)  
           Catch ex As Exception  
             Throw ex  
           Finally  
             'Close connection  
             conn.Close()  
           End Try  
         End Using  
       End Using  
     Catch ex As Exception  
       'Error occured  
       Throw ex  
     End Try  
     If dt IsNot Nothing AndAlso dt.Rows.Count > 0 Then  
       'Record came back --Note: This check is all you need.  
       'The Account Name is already used  
       rtn = False  
     Else  
       'As of now the account should be unique.  
       rtn = True  
     End If  
     Return rtn  
   End Function  
   ''' <summary>  
   ''' Check the FULLACCT table in the database to see if an account is conflicting.  
   ''' </summary>  
   ''' <param name="AccountName">The newly built account name</param>  
   ''' <returns>Returns True if it is able to find the conflicting account name in the database.</returns>  
   ''' <remarks></remarks>  
   Private Function _checkIsAccountConflicting(ByVal AccountName As String, ByVal FirstName As String, ByVal LastName As String) As Boolean  
     'Setup the return object  
     Dim rtn As Boolean = False  
     Dim dt As New DataTable  
     'Set up the sql command string  
     Dim strCommand As String = "SELECT TOP 1 firstName,lastName FROM TABLENAME WHERE userName = '" + AccountName + "';"  
     'Get the connection string  
     Dim ConnString As String = "Data Source=SERVERNAME\INSTANCE;Database=DATABASE;Trusted_Connection=yes"  
     Try  
       'Setup the connection  
       Using conn As New System.Data.SqlClient.SqlConnection(ConnString)  
         Using cmd As New System.Data.SqlClient.SqlCommand(strCommand, conn)  
           Try  
             'Open Connection  
             conn.Open()  
             cmd.CommandTimeout = 0  
             Dim da As New SqlDataAdapter(cmd)  
             'Execute the command  
             da.Fill(dt)  
           Catch ex As Exception  
             Throw ex  
           Finally  
             'Close connection  
             conn.Close()  
           End Try  
         End Using  
       End Using  
     Catch ex As Exception  
       'Error occured  
       Throw ex  
     End Try  
     If dt IsNot Nothing AndAlso dt.Rows.Count > 0 Then  
       'Possible conflict found, check first and last name to see if it indeed could be a conflict.  
       If dt.Rows(0)("lastName") IsNot Nothing AndAlso Not IsDBNull(dt.Rows(0)("lastName")) _  
         AndAlso dt.Rows(0)("lastName").ToString().ToLower = LastName.ToLower() Then  
         If dt.Rows(0)("firstName") IsNot Nothing AndAlso Not IsDBNull(dt.Rows(0)("firstName")) _  
           AndAlso dt.Rows(0)("firstName").ToString().ToLower = FirstName.ToLower() Then  
           'If first and last name exists and are the same as the target identity, then this is possibly a conflicting account.  
           rtn = True  
         End If  
       Else  
         'If things did not check out with first and last name, then there is not a conflict.  
         rtn = False  
       End If  
     Else  
       'No conflicting previous account with an "X" found.  
       rtn = False  
     End If  
     Return rtn  
   End Function  
 #End Region  
 End Class  

I hope this helps.  If there are any questions, feel free to ask!

-jose the admin

Friday, April 5, 2013

PowerShell, FIM, and overcoming surprises when migrating from Exchange 2007 to 2010

So, there I was, just minding my own business, developing a FIM solution in my lab after reconfiguring Exchange provisioning from 2007 to 2010 and all was well in the world when, all of a sudden, I noticed that users were no longer maintaining the same standard mailbox configurations for my organization!

After some research, I discovered that requests from the portal to take a workflow action against newly provisioned user mailboxes were receiving PowerShell errors.

In the previous design, when provisioning Exchange 2007 mailboxes, the Exchange management tools were installed on the server hosting the FIM service.  This was so our PowerShell scripts, which update our mailboxes via FIM portal workflow/MPR, could utilize the locally installed Exchange module required.  However, the old commands could no longer be run against 2007 tools and a 2010 CAS.  What to do????

Establishing a remote connection was the answer.  Much like the AD MA Exchange 2010 configuration, which essentially provisions using the PowerShell URI defined against the Exchange CAS server, I decided to change our scripts to follow the same approach.  The result is the following:


So now, I am actually updating our mailboxes by executing our commands remotely on our CAS!  That fixed our issue, only after giving me a slight scare.  :)

-jose the admin

Thursday, March 21, 2013

FIM: Understanding and handling "accountExpires" with care

Through much research, trial, and LOTS of error, I have, what I believe, a very good solution for processing the flow of values of the "accountExpires" attribute from Active Directory on through to the FIM portal, and vice versa.  I found that the portal and Active Directory handle both the values and the states of these attributes quite differently.

To clarify, this is specifically the "accountExpires" attribute bound to the user object type in Active Directory.

First, to really understand "accountExpires" from a technical standpoint, I recommend reading the following link: http://www.rlmueller.net/AccountExpires.htm

This really opened my eyes to what administrators actually see in ADUC, although not necessarily the truth.  What is golden, though, is that the integer value found in Active Directory is, in fact, correct.  To bad we can't read it!

Moving on, we have a defined attribute in the FIM portal schema known as "ExpirationTime" that we will use to mirror the same values and purposes of "accountExpires" in AD.  This, differing from the attribute type in AD, is a DateTime attribute and not a 64-bit (large) integer. 

Next, we define the "expirationTime" attribute in the metaverse designer for the person object as String(indexed), yet the check box to actually index is left unchecked (you want to indicate indexing on attributes that are used for joining objects from directory sources to existing metaverse objects). 

NOTE: When your import rules are defined for the required MAs for this attribute, you will then want to set this attribute's flow precedence to equal precedence (in our solution, there was a need to be able to change the expiration time from the portal, our HR system, or AD.  Here, however, we focus on the portal and AD only.

Thus, we end up with flow rules that, at a high level, state the following:

Basic idea of flow rules for accountExpires configured in Sync Manager

 
Specifically, I have my FIM-MA flow rules for ExpirationTime as follows:
 
ExpirationTime (portal) <-- expirationTime (MV)  [Allow Nulls]
ExpirationTime (portal) --> expirationTime (MV)
 
Both rules are direct rules by nature of the FIM-MA.  Next, I have my AD management agent configured as follows:
 
accountExpires (AD) <-- expirationTime (MV)
accountExpires (AD) --> expirationTime (MV)
 
Both of these rules have advanced mapping type with rules extension.  I have called the flow rule name "expirationTime" in both cases as the code segregates both import and export rules.
 
Now that we have established the configurations required in the FIM Sync engine, we now move on to the fun part: the required rules extensions for AD.
 
The following code is defined for our import and export rule extensions:
 
Public Sub MapAttributesForImport(ByVal FlowRuleName As String, ByVal csentry _ As CSEntryByVal mventry As MVEntry) Implements IMASynchronization.MapAttributesForImport
 
Dim dtFileTime As DateTime
 
Select Case FlowRuleName
 
   Case "expirationTime"
     If Not csentry("accountExpires").ToString() = "" And _
        Not csentry("accountExpires").IntegerValue = 9223372036854775807 Then
           dtFileTime = DateTime.FromFileTime(csentry("accountExpires").IntegerValue)
           If dtFileTime.ToString("yyyy") = "1600" Then
                If mventry.Item("expirationTime").IsPresent Then
                     mventry.Item("expirationTime").Delete()
                End If
           Else
               mventry.Item("expirationTime").Value = _
               dtFileTime.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.000'")
               '("yyyy-MM-ddT" + "00:00:00.000")
           End If
      Else
         If csentry("accountExpires").IntegerValue = 9223372036854775807 Then
             If mventry.Item("expirationTime").IsPresent Then
                   mventry.Item("expirationTime").Delete()
             End If
         End If
      End If
 
Public Sub MapAttributesForExport(ByVal FlowRuleName As String, ByVal mventry As MVEntry, _ ByVal csentry As CSEntry) Implements IMASynchronization.MapAttributesForExport

Dim provider As CultureInfo = CultureInfo.InvariantCulture
Dim dtFileTime As DateTime
 
Select Case FlowRuleName
 
    Case "expirationTime"
        If mventry.Item("expirationTime").IsPresent Then
             If Not mventry.Item("expirationTime").Value.ToString() = "" Then
                 If Not mventry.Item("expirationTime").Value = Nothing Then
                    dtFileTime = _
                    DateTime.ParseExact(mventry.Item("expirationTime").Value, _
                      "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.000'", provider)
                    csentry("accountExpires").IntegerValue = dtFileTime.ToFileTimeUtc()
                 End If
             Else
                 csentry("accountExpires").IntegerValue = 0
             End If
         End If
 
There is one more moving piece, but, with all that said, let me explain why I arrived at the above.
 
When it comes to converting dates and integers, the above tends to get it done.  I will go into some details on that, but the interesting part is setting the account to never expire (AD), or removing the date in the portal.
 
I noticed in Active Directory that, when you set an account to never expire, the integer value becomes zero or the max value (9223372036854775807). However, truthfully, we do not want to flow a "datetime" value when this is the case, since now it will never expire.  In fact, we want to remove the fact that a date is there at all.  Thus, we delete the attribute and value all together in the metaverse, which in turn flows and deletes the attribute and value on the object in the portal.  This is possible because we are allowing nulls to export into the portal.
 
Now, the real interesting part happens when you try to remove the value in the portal.  I noticed at that point that FIM actually removes the attribute right then and there!  So the next thought might be, "Well, just set the AD export rule to allow nulls as well.  Simple!"  NOT.  The operation of deleting the attribute from Active Directory is not allowed and you get the lovely "cd-error Illegal modify operation" error.
 
 
In short, adding a second, remaining attribute to the export flow rule would maintain the processing of the rules extension.  However, I found issues with that, and some folks stating that it would help to add an attribute that would change with it.  All this was very messy.  So, I opted for a different approach.
 
What I came up with was to execute a script using the portal workflow/MPR/set transition method.  The set would include all those with an expiration time defined.  The criteria for the set was basically ExpirationTime prior to today and after today.  Weird, but it works.  Upon leaving the set (blanking the ExpirationTime value out via the portal), the workflow would kick off, running a script against AD to set the account to never expire.  This way, the account IMMEDIATELY is set, and the change will flow as expected, deleting the attribute across the metaverse and the portal.
 
So, that's how I dealt with setting accounts to never expire, but what about the actual time values?
 
Long story short (the above still makes this a LONG story...thanks for holding on!), the metaverse value is read as UTC time.  So, if I am in CST (GMT -6) (and I am) as set in the portal configuration, while the portal would read 3/21/2013 12:00 AM, the metaverse value would read 2013-03-21T05:00:000, and the integer value in AD would be 130083156000000000.  This is why if you set the time to expire in AD, it states "End of:", as in midnight the day after you set in ADUC.
 
Using the above solution, I am able to flow all of my times and lack thereof flawlessly.  I noted that, after performing all of this research and testing, there was not any one concise place to find all of this.  Thus, this post.
 
Hope this helps someone out there!
 
Best regards,
 
jose the admin


Wednesday, March 20, 2013

Hello, again...

So, it has been a long while since I updated this blog site, but I plan on ramping back up.  I feel that I have been making lots of very helpful discoveries, as well as accumulating lots of obscure knowledge, that could be helpful to many folks out there.

What kind of knowledge, you may ask?  Why, of course I am referring to all things Microsoft, as well as some networking and security tidbits from time to time.  Currently, I am spending most of my time working with Microsoft Forefront Identity Manager 2010, and, as a result, work very closely with Active Directory, Exchange, and the like.  However, I am also beginning to explore Server 2012 and Windows 8 (late) in detail.

I will be updating my pages regularly as time allows, so stay tuned.  Also, feel free to follow me on twitter @josel_garza.

Look forward to your comments and questions!

Regards,

jose the admin