0
Not a bug

Inconsistent import/export treatment of accountExpires AD attribute values

Adrian Corston 6 months ago in UNIFYBroker/Microsoft Active Directory updated by Beau Harrison (Senior Software Engineer) 5 months ago 5

When I import accountExpires for an AD user object as a Date field type the value I see matches the value in AD Users & Computers:

However when I export a value to that field (from a locker via a link in UNIFYBroker/Plus) it is set to the previous date in AD:

The Import and Export behaviours should match, or else there will be a repeated set/read/set/read loop of the value because the value read back on import won't ever match the value set on export.

For reference, for most HR systems the "end date" or "termination date" is the last date on which an employee has access, which matches the behaviour seen here for Import.

Affected Versions:
Fixed by Version:

Answer

Answer

Using Timestamp is the best approach.  It must be in UTC to work correctly, however.  To achieve this, I had to import my date field as a string, then use the following adapter transform to generate EndDate (for use in Time Offset Flag transforms) and EndTimestampUTC (for mapping to accountExpires on an AD connector):

foreach ($Entity in $entities)
{
    $EndDateString = $Entity["EndDateString"].Value
    $EndDate = $Null
    $EndTimestamp = $Null
    if ($EndDateString) {
        # EndDate is a [DateTime] object of kind "Unspecified"
        # Its value is midnight at the start of the last day of the employee's access, as interpreted in the local timezone
        # Note: Adjust this if $EndDateString is not in m/d/yyyy format.
        $EndDate = [DateTime]::ParseExact($EndDateString, "M/d/yyyy", [System.Globalization.CultureInfo]::InvariantCulture)

        # EndTimestamp is a [DateTime] object of kind "Utc"
        # Its value is the UTC (GMT) representation of the exact second when the user account should be disabled -
        # in this case midnight in the local timezone at the start of the day after the End Date. If you need access to be terminated earlier than this
        # (e.g. 5pm in the local timezone on their last day) then change the .AddDays(1) accordingly.
        # Make sure the timezone specified is correct for the End Date specified.
        $EndTimestampUTC = [TimeZoneInfo]::ConvertTimeToUtc($EndDate, [TimeZoneInfo]::FindSystemTimeZoneById('AUS Eastern Standard Time')).AddDays(1)
    }
    $Entity["EndDate"] = $EndDate
    $Entity["EndTimestampUTC"] = $EndTimestampUTC
}

+1
Under review

Hi Adrian

Can't find a Voice ticket about it, but I believe this has come up before and is a quirk of AD. Basically once updated value is shown by the AD dialogue to be the last active day, while natively set expiries are show as the first expired date. See this forum discussion for more information, its related to using PowerShell but I believe the same applies to Broker.

The main thing is that the reimported value is the same as what was exported. Have your run an import after an expiry date has been exported? Does the connector entity still contain the correct value?


I also notice you're using the "Date" schema validator for the accountExpires attribute. In past projects people generally use "Timestamp" validator. You may find that gives more accuracy also, so that there's no timestamp-to-date-involving-timezone shenanigans happening.

Hi Beau,

Yes, AD Users & Computers does display the date of the day prior to the millisecond at which the accountExpires field indicates that the user account should be disabled (as stored by AD as a FileTime value internally).

I tried the re-import and the exported value does come back in correctly.  Looking deeper, I note that the accountExpires value on the ADU&C Attribute Editor is showing up with an 11:00:00 offset (equal to the current timezone offset) suggesting that the accountExpires value from UNIFYBroker has been treated as UTC.  The original value in that AD user account object's field probably didn't have had an offset, which might explain why was imported as the same date (even though perhaps it should not have been).  With that in mind, I think Matt's suggestion that I use a Timestamp is probably necessary, to avoid confusing non-day-boundary truncation.  I'll set it to the UTC value for midnight local time on the day on which the account should be disabled and see how that goes.

Answer

Using Timestamp is the best approach.  It must be in UTC to work correctly, however.  To achieve this, I had to import my date field as a string, then use the following adapter transform to generate EndDate (for use in Time Offset Flag transforms) and EndTimestampUTC (for mapping to accountExpires on an AD connector):

foreach ($Entity in $entities)
{
    $EndDateString = $Entity["EndDateString"].Value
    $EndDate = $Null
    $EndTimestamp = $Null
    if ($EndDateString) {
        # EndDate is a [DateTime] object of kind "Unspecified"
        # Its value is midnight at the start of the last day of the employee's access, as interpreted in the local timezone
        # Note: Adjust this if $EndDateString is not in m/d/yyyy format.
        $EndDate = [DateTime]::ParseExact($EndDateString, "M/d/yyyy", [System.Globalization.CultureInfo]::InvariantCulture)

        # EndTimestamp is a [DateTime] object of kind "Utc"
        # Its value is the UTC (GMT) representation of the exact second when the user account should be disabled -
        # in this case midnight in the local timezone at the start of the day after the End Date. If you need access to be terminated earlier than this
        # (e.g. 5pm in the local timezone on their last day) then change the .AddDays(1) accordingly.
        # Make sure the timezone specified is correct for the End Date specified.
        $EndTimestampUTC = [TimeZoneInfo]::ConvertTimeToUtc($EndDate, [TimeZoneInfo]::FindSystemTimeZoneById('AUS Eastern Standard Time')).AddDays(1)
    }
    $Entity["EndDate"] = $EndDate
    $Entity["EndTimestampUTC"] = $EndTimestampUTC
}