HowTo: The IDM Workflow Loop Route - Disable Inactive Users


In many cases there is a need to add loops to a workflow.

Such loops may be required if you need to execute repetitive tasks, or to perform tasks on multiple objects.

This article shows both:

  • the sample task will disable users who have been inactive for a specified number of days

  • the sample task is repeated daily until explicitly stopped

The workflow sample outlined in this article has the purpose to deactivate users who have not logged in for a specified number of days.

For this purpose, the example uses two loops:

  • The outer loop is repeated every day. It scans for users who have not logged in for longer than the specified time

  • The inner loop goes through the result list and perform an Entity activity (disable the user) for each matching user.

Adding a time-controlled loop is rather simple.

  • behind your custom actions, append a dummy approval activity with a timeout of your choice

  • on timeout, go back to the start of your custom actions

Adding data-dependent loops is only slightly more complicated, since you need to add some custom ECMAscript to evaluate the data and control the loop.

Here's the basic logic for a workflow that, once started, will automatically run every day, identify the inactive users, and disable each user in the list:

  1. Start Form

    Start your workflow and enter the inactivity interval. The sample uses a list with the options "30 days", "60 days", "90 days", "180 days", and "360 days"

    The form will also show what users will be affected during the first run.

    Internally, a Query is used, and you will probably need to adapt this query to your own needs:

    The sample query is named "CoolSolution_inactiveUsers" and perform a search on the container "OU=users,O=utopia" with a filter "( && ( loginDisabled != true )( loginTime < [param] ))"

  • Calculate the Criterion Date

    If we want to identify the users who have not logged in for 90 days, we need to calculate the criterion date with something like "criterionDate = ([current date] - 90 days)", before finding all users whose las login date is earlier that this criterion.

    We store the calculated criterionDate in flowdata (flowdata.start/criterionDate).

    This is the code to calculate the target date:

    function getCriterionDate( )
    var result = "00000000000000Z";
    var daysOfInactivity = flowdata.get('start/request_form/daysOfInactivity');
    var someTimeAgo = new Date();
    someTimeAgo.setDate( someTimeAgo.getDate() - daysOfInactivity );

    result = ""
    ( // make date 8-digit
    10000 * someTimeAgo.getUTCFullYear()
    100 * (someTimeAgo.getMonth() 1)

    ( // make time 8-digit
    10000 * someTimeAgo.getUTCHours()
    100 * someTimeAgo.getUTCMinutes()
    catch ( ex ) {}
    return( result );


  • Query for Inactive Users

    Here, we run the query, passing the calculated criterionDate as parameter. The query will return an unknown number of matching users, which we store in flowdata (flowdata.start/inactiveUsers/DNs).

    This is the code to run the query and store the list:

    function getInactiveUsers()
    var result = new java.util.Vector();
    var daysOfInactivity = flowdata.get('start/request_form/daysOfInactivity');
    var criterionDate = flowdata.get('start/criterionDate');
    result = IDVault.globalQuery( "CoolSolution_inactiveUsers", {"someTimeAgo": criterionDate });
    catch ( ex ) {}
    return( result );


  • Inactive Users Found

    If no inactive users were found, we skip the next actions, jump to step i), and wait for the next polling loop on the next day.

    If inactive users were found, we continue with step e)

    ( flowdata.get('start/inactiveUsers/DNs').length > 0 )

  • Get Next User

    So, we have found a list of one or more inactive users.

    This step will simply get the first user from this list and store the DN in flowdata (flowdata.start/inactiveUsers/nextDN).


  • User Found?

    This conditional activity checks if we have identified the next user in the list.

    The condition will return "true" as long there are more users to process; in this case, we'll continue with step g).

    It will return "false", after all users have been processed; in this case we'll goto step i) to wait for the next polling loop on the next day.

    ( flowdata.get('start/inactiveUsers/nextDN').length > 0 )

  • Disable this User

    This entity activity will set the selected user's (flowdata.start/inactiveUsers/nextDN) attribute "Login Disabled" to true.

  • Remove this User from List

    We have now just disabled the first user in the result list. In this step, we recalculate the list and remove the user that we have just processed.

    We continue with step e) to get the next user in the reduced list.

    This code removes the first list entry:

    function removeFirstElement()
    var result = new java.util.Vector();
    var objectsRemaining = flowdata.getObject('start/inactiveUsers/DNs')

    // start loop with second element
    for ( var i=1; i < objectsRemaining.size(); i )
    result.add( objectsRemaining.get(i).getFirstChild().getNodeValue() );
    catch (e) {}
    catch (e) {}
    return result;
    } ;


  • Await Next Scan

    We have now processed all users in the list, coming from step d) or from step f), we will use this dummy approval activity to wait for its timeout, i.e., for the next polling interval.

    If the selected addressee does nothing, the workflow will timeout and continue with step b)

    If an admin opens the approval form and presses "Deny Request", the whole workflow ends with step j)

  • We're done

The sample workflow for this article has been attached and can be imported into designer.

It consists of

If you are not working with Novell's demo environment "Utopia", you will probably have to modify the trustees of the workflow and the search base for the query.

Developed and tested on Identity Manager version 3.7.0


How To-Best Practice
Comment List
  • Hi,

    I am confusing with the below statement, if you are quering loginDisabled == true, then finally you are setting loginDisabled == true through entity.

    "The sample query is named "CoolSolution_inactiveUsers" and perform a search on the container "OU=users,O=utopia" with a filter "( && ( loginDisabled == true )( loginTime "

    pls correct it. may be its loginDisabled not equal to True in the query.