A common requirement is that user accounts should go through a disabled stage of some length before being deleted. This makes excellent sense, particularly in AD with its fastidiousness concerning SIDs.
In this post I outline a way to achieve this in AD using a datestamped attribute, export flow rules and provisioning code.
The General Approach
For this example I use the info attribute in AD to keep a record of the latest change, for example “Disabled on 12/10/2008”.
Say we are retaining disabled accounts for 90 days – once the provisioning code sees that the Disabled date is more than 90 days in the past, and as long as the account is still actually disabled, the account will be deprovisioned.
Disabling the Account
Now firstly I am assuming you have some kind of status attribute in the metaverse which has just been changed to “inactive”. You feed this into an export flow rule which updates the userAccountControl attribute.
Case "export_userAccountControl" Const ADS_UF_ACCOUNTDISABLE As Integer = &H2 Const ADS_UF_NORMAL_ACCOUNT As Integer = &H200     Dim currentValue As Long   If csentry("userAccountControl").IsPresent Then        currentValue = csentry("userAccountControl").IntegerValue   Else        currentValue = ADS_UF_NORMAL_ACCOUNT    End If    Select Case mventry("status").Value    Case "active"        csentry("userAccountControl").IntegerValue = (currentValue Or ADS_UF_NORMAL_ACCOUNT) _                                                        And (Not ADS_UF_ACCOUNTDISABLE)      Case "inactive"           csentry("userAccountControl").IntegerValue = currentValue _                                                        Or ADS_UF_ACCOUNTDISABLE    End SelectÂ
Moving the Account
A lot of people like to keep the disabled accounts in a seperate OU. This is a straight-forward case of determining the OU in your provisioning code based on the status, and then renaming the DN if necessary.
The following snippet is not a complete provision sub, but shows the code you need to work in to get the accounts moved.
Const ACTIVE_OU = "OU=Users, dc=mydomain, dc=com" Const INAVTIVE_OU = "OU=Disabled, OU=Users, dc=mydomain, dc=com" Const MA_NAME = "AD" Dim MA As ConnectedMA = mventry.ConnectedMAs(MA_NAME) Dim ShouldExist as Boolean 'Not shown here - some sort of logic to work out this value     'Generate the expected DN     Dim RDN As String = "CN=" & mventry("displayName").Value    Dim Container As String     Select Case mventry("status").Value.ToLower            Case "active"                Container = ACTIVE_OU            Case "inactive"                Container = INACTIVE_OU            Case Else                Throw New UnexpectedDataException("User Status " & mventry("status").Value & _                                                  " is not supported for user provisioning in " & MA_NAME)                Exit Sub        End Select        Dim DN As ReferenceValue = MA.EscapeDNComponent(RDN).Concat(Container)
If ShouldExist and MA.Connectors.Count = 1 Then        'Check if rename needed        Dim CSEntry As CSEntry = MA.Connectors.ByIndex(0)        If CSEntry.DN.ToString.ToLower <> DN.ToString.ToLower Then            CSEntry.DN = DN        End If End If
Recording the Disabled Date
Now, this is where it gets a little tricky.
I want to update the info attribute with the date the account was disabled. You could base this on status however the change in status alone does not prove the account was actually disabled. I prefer to base this change on the hard evidence of the userAccountControl attribute – the downside being that I have to flow userAccountControl back in before I can update info – that is, the info will be updated on the sync after the account was actually disabled.
So, create a metaverse attribute for userAccountControl and then create a direct Import flow rule to flow it into the metaverse.
Next, create an Advanced export flow rule for info, as follows.
Case "export_info" 'AD userAccountControl must have been flowed into metaverse 'Info is updated on the sync following userAccountControl being changed. If mventry("userAccountControl").IsPresent AndAlso _ (mventry("userAccountControl").IntegerValue And ADS_UF_ACCOUNTDISABLE) = ADS_UF_ACCOUNTDISABLE Then 'Account is disabled If Not csentry("info").Value.Contains("Disabled") Then csentry("info").Value = "Disabled on " + Today.ToString("d") End If Else 'Account is enabled If csentry("info").Value.Contains("Disabled") Then csentry("info").Value = "Enabled on " + Today.ToString("d") End If End If
And finally, the Deletion
So, we now should have accounts successfully disabled and tagged with the date it happened.
The final step is to add the Deprovisioning instructions into the provisioning code – and for this you are going to need that info attribute in the metaverse as well. So create another metaverse attribute, and the corresponding Direct import flow rule.
Now you can update your provisioning code to decide when the time is right to delete the account. Note in the provisioning snippet above I made use of a boolean called ShouldExist. When I have determined that ShouldExist is FALSE I can call my csentry.Deprovision method. The code will be something like this:
Select Case mventry("status").Value.ToLower Case "active" ShouldExist = True Case "inactive" ShouldExist = False If mventry("userAccountControl").IsPresent AndAlso _ mventry("info").IsPresent Then If (mventry("userAccountControl").IntegerValue And ADS_UF_ACCOUNTDISABLE) = ADS_UF_ACCOUNTDISABLE Then 'Account disabled ShouldExist = True If mventry("description").Value.Contains("Disabled") Then Dim strDate As String = mventry("description").Value.Replace("Disabled on ", "") Dim disabledDate As DateTime = Convert.ToDateTime(strDate) If Now.Subtract(disabledDate).Days > KEEP_DISABLED_DAYS Then ShouldExist = False End If End If Else 'Account enabled ShouldExist = True End If End If Case Else Throw New UnexpectedDataException("Unexpected User Status " & mventry("status").Value) Exit Sub End Select . . . If Not ShouldExist And MA.Connectors.Count = 1 Then Dim CSEntry As CSEntry = MA.Connectors.ByIndex(0) CSEntry.Deprovision() End If
Downsides to this approach
The main weakness I would confess to is that the system can be effected by changes made directly in AD. If someone changes the date in the info field the account may be deleted sooner or later – though I have made good use of this as a simple way to extend the life of a particular account. The main problem would occur if someone deleted all text from the info field, leaving MIIS with no information. If you need a system that is entirely separated from this AD dependance then I would recommend an extra SQL MA to keep track of the dates and statuses.
The other potential concern is the fact that the info attribute is updated on the second sync. This is fine if you’re happy to just have a date in the field – not so fine if you want an exact time. You can minimise the problem by running two Export/DIDS operations in a row, but even then the time won’t be exact. Unfortunately I’ve never found a great solution to whole description-updating issue – for more on that see this post.
I’m new to your blog and am learning a lot from it. Thanks. I have a question on the “Recording the Disabled Date” section here. Wondering why you have to flow the UAC attribute into the metaverse for this to work? Why can’t you read it from the connector space object instead? Thanks again.
Hi there Steven,
the reason I’m flowing UAC into the metaverse is because I want to use it as input for my export flow rule for the info attribute. You can’t use other CS attributes in a flow rule.
Are you sure?? From what I read in my Oxford course manual, all of the CS object attributes are available as Read-only in EAF rules.
Was this section perhaps talking about import flow rules? All cs attributes are available for flow rules into the metaverse. There’s no way, that I know of, to select a CS attribute for an output flow rule – but if you manage to do it do let me know!
No, you don’t have to select them for export flow rules. Here’s what the Oxford course manual says about import and export flow rules concerning what attributes are passed:
[Quote]Import Flow Rules: The method is passed the CS Object, but only including those attributes that you selected, and read-only; it is passed the MV object with target attribute as read-write and all other attributes read-only.
Export Flow Rules: The method is passed the MV Object, but only including those attributes that you selected, and read-only; it is passed the CS Object with the target attribute as read-write and all other attributes read-only.
So both methods are passed both the connector space object and the Metaverse object, but with different attributes being read-write (only the one target attribute), read-only or not available. Things are arranged this way for performance reasons. Attempting to access source attributes that are not available will generate an error (AttributeNotDefindAsSourceException), as will attempting to write to a read-only attribute.{/Quote]
So, if I understand that correctly, you always have access to all target object attributes as read-only (but don’t/can’t select them in the UI) except for the one you’re flowing to which is obviously read-write. I guess I’ll have to try that to verify. I was just wanted to make sure that I was reading correctly what you were saying. Thanks.
Hmmm – could read that both ways really. Honestly I’ve never tried accessing another CS attribute in an EAF – let me know how it goes.
Carol, hope you don’t mind another question. I’m curious how the Deletion part of your example would get triggered–would a full sync be needed or what on a delta sync would trigger an mv object change so this code would run for an object due for deletion?
I ask because I’m using parts of your technique for a similar task but not sure how to trigger it without a full sync. Thanks so much.
Deletions will be triggered with a delta sync.
Did you test whether you could access the other CS attributes from a flow rule?
i’m using your code, but i get an error in Recording the Disabled Date in the
If Not csentry(“info”).Value.Contains(“Disabled”) , Attribute info is not present. i confgiured the Source Managment Agent to Direct Import EmpID -> Info, and ADMA to Direct Import info -> Info
and Advanced Export info <- info,userAcountControl.
If the info attribute is unpopulated in the metaverse you can get this error. Have you still got the test for mventry(“info”).IsPresent in the If statement?
the if (mventry[“info”].IsPresent) evaluated as true.
OK, well there’s bound to be an explanation. Have you gone through in debug mode to see exactly where the error is occurring?
its ok, i found the cause of the problem, i added a value to the info when provisioning the user account to be “Created on … “.
Carol, Please tell me how to configure Deprovisioning in both managment agents and in the Metaverse Desginer.
OK that’s good you sorted it out.
To delete objects you must enable deletions with the “Stage a delete…” option on the Deprovisioning tab of the MA, then use the csentry.Deprovision() command in your provisioning code.
why the provision method called again after calling the csentry.Deprovision()
The provision sub is run after *every* change to a metaverse object – and a deletion is considered a change. You have to be prepared for this in your code, by always testing for connectors and existance of attributes.
Carol, great post! One question though…
You mentioned in the comments that the final deprovision occurs during a delta sync. However, I must be missing something since a delta sync on the disabled object will never occur unless something changes on the Active Directory side to cause the account to be imported during a delta import.
Could you elaborate on how the delta sync on the object is being triggered?
Actually I think you’re right. Deletions can be triggered in a delta sync – but not in this case where the object has been sitting there unchanged for some time. Good catch!