sabell2012
Mega Sage
Mega Sage

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

DIFFICULTY LEVEL:   INTERMEDIATE to ADVANCED

Assumes knowledge and/or familiarity of several different areas in ServiceNow.

____________________________________________________________________________

Recently two of our developers (adam.keller, travisbell) came to me and asked if it were possible using Service Catalog and Workflows to do the following:

  1. Add multiple Users to a Group with a dynamic request for Approval
  2. Add a single User to multiple Groups with a dynamic request for Approval
  3. Each Group Approval Request/Response must not hold up the other Group Approvals.
  4. The Group Manager would be the approver for each group.

I thought the answer would be an easy one.   Boy, was I wrong!

What I attempted:

  1. My first thought was to simply use the Approval - Users Activity, and feed it a dynamic group for approval.   However, even though it would normally work for requesting group approvals it would not work to fulfill the third and fourth requirements.
  2. I next tried to construct a workflow loop using the Approval - Users Activity.   Essentially take all of the users and present them as a whole to each group for approval.   This worked fine, but that pesky third requirement got in the way again.
  3. Next I decided to try kicking off multiple workflows from a calling workflow.   This almost worked.   It behaved VERY strangely.   For example, with two groups to be approved one would wait for approval like it was supposed to, but the other one would auto-approve!   Probably a timing issue.   I will have to dig deeper on this and see if I can find out why.
  4. I then decided to try the same thing with Events.   So created a custom event, complete with Script Action to be called from the workflow.   Essentially this is the same as just calling a workflow.   Sure enough same behavior as my second scenario!   First one waited for approval, the second group auto-approved!   Yeah, gotta be timing...
  5. The parallel RITM approach just HAD to be the solution here, but how to do that?   I broached the subject with b-rad (Brad Tilton), and he pointed me to a response to a similar problem stated on the Community.   duffyb (Barbara Duffy) (response link) had run into how to properly create a RITM in code.   He said he had used her solution, and it had worked for him.   The RITM would then call it's requisite workflow and the approvals would work correctly.   Tried it, and it failed! This time the groups and users values were not making it from the calling workflow to the new RITMs!   It was creating the RITMs fine, but the values weren't showing up.
  6. In the wind-down at a meeting, Brad and I were discussing the issue, and mamann (Mark Amann) who was present said he would like to know more as it sounded familiar.   Sure enough Mark had also solved something very similar to this.   Same kind of thing where a RITM was being created in code, but the variables passed into the workflow did not populate.   He had the final bits of the puzzle:   You have to throw a timer in the RITM workflow to wait until the passed-in variables actually arrive!   Actually arrive?   Looks like there is a timing thing going on after all!   Sure enough when I put that last bit of magic into the code it worked!


Who says networking doesn't get results?!   🙂


So I decided to share.   It is obvious that this problem is recurring, and not too many people were aware of the solution.   I certainly wasn't!


Prerequisites

  1. Some knowledge of creating Workflows with the Workflow Editor
  2. Some knowledge of creating Service Catalog Items
  3. Some knowledge of creating and scripting Workflow activities



Lab 1.1: Create the Add Users to Groups Workflow

1. Navigate to Workflows -> Workflow Editor.   The Workflow Editor will be displayed.

2. Click on the "+" button to create a new workflow.

a. Name: Add Users to Groups

b. Table: Catalog Item [sc_cat_item]

c. If condition matches:   Run the workflow

d. Click on the Submit button to create the workflow.

3. Navigate in the workflow to Core -> Utilities and place a Run Script Activity between the Begin and End activities.

a. Name: Initilialize

b. Script:



// interestingly because of the timing issues I had to use

// a hardcoded context value with my identifier

var identifier = 'Add Users To Groups.' + activity.name;

var message = '--->\n';

// From the initial RITM

var groups = current.variables.groups + '';

var users = current.variables.users + '';

var groupList = [];

// if we have more than one they will be comma delimited

if (groups.indexOf(',') > -1) {

  groupList = groups.split(',');

}

else {

  groupList.push(groups);   // only one group

}

// just a few debug messages - remove these when going to QA

message += 'groups: ' + groups + '\n';

message += 'groupList: ' + groupList.length + '\n';

message += 'users: ' + users + '\n';

message += 'current.request: ' + current.request;

// get the sys_id of the RITM to create. In this case we

// want to create a new RITM for each group that we want

// to request access from

var requestItemTemplate = new GlideRecord('sc_cat_item');

requestItemTemplate.get('name', 'Add Users to Group');

var ritmTemplateID = requestItemTemplate.sys_id + '';

// loop through and create a new RITM for each group,

// attached to the parent RITM

for (var i=0; i < groupList.length; i++) {

  var group = groupList[i] + '';

 

  // undocumented feature - no API definition unfortunately

  // appears to do the following:

  // - Create new RITM based on template

  // - Copy all variables from the template to the new RITM

  // - Associate the new RITM to the current RITM

  // - Trigger the workflow (state = 2)

  var requestHelper = new GlideappCalculationHelper();

  requestHelper.addItemToExistingRequest(current.request + '', ritmTemplateID + '', 1);

  // Not sure of the function or necessity of this, but it is

  // always present with the 2-3 examples I found

  requestHelper.rebalanceRequest(current.request + '');

 

  // Find our new RITM (it will be the most recent right?)

var reqItem = new GlideRecord('sc_req_item');

  reqItem.addQuery('request', current.request + '');

  reqItem.addQuery('cat_item', ritmTemplateID + '');

  reqItem.orderByDesc('number');

  reqItem.setLimit(1);

  reqItem.query();

 

message += '---> TOTAL: ' + reqItem.getRowCount() + '\n';

  // Now set the variable values of our new RITM to the targeted

  // group for approval, and the users we want added. Even after

  // this update is done it takes a bit for the new RITM workflow

  // to wake up to these new values

if (reqItem.next()) {

    message += '---> Updating: ' + reqItem.sys_id + '\n';

    reqItem.variables.group = group + '';

    reqItem.variables.users = users + '';

    reqItem.parent = current.sys_id + '';

    reqItem.update();

  }

}

// debug - write out all of our results to the system log

gs.log(message, identifier);

c. Click the Submit button to save your activity.



Lab 1.2: Create the Add Users to Group Workflow

1. From the Workflow Editor click on the "+" button to create a new workflow.

a. Name: Add Users to Group

b. Table: Catalog Item [sc_cat_item]

c. If condition matches:   Run the workflow

d. Click on the Submit button to create the workflow.

2. Navigate in the Workflow Editor to Core -> Timers and place a Timer Activity after the Begin.

a. Name: Wait for Vars

b. Timer based on: A user specified duration.

c. Duration: 15 seconds.

d. Click on the Submit button to create the Timer.

3. Navigate to Core -> Conditions and place an If Activity after the Timer Activity.

a. Name: Are Vars Filled In?

b. Advanced: Checked

c. Script:

answer = ifScript();

function ifScript() {

  if (JSUtil.notNil(current.variables.group) && JSUtil.notNil(current.variables.users)) {

    return 'yes';

  }

  return 'no';

}

d. Click on the Submit button to create the If Activity.

4. Navigate to Core -> Utilities and place a Run Script Activity between the Begin and End activities.

a. Name: Initilialize

b. Script:

var identifier = context.name + '.' + activity.name;

var users = current.variables.users + '';

var group = current.variables.group + '';

var messages = '--->\n';

// debug - edit out for production

messages += '---> current.variables.users: ' + current.variables.users + '-' + identifier + '\n';

messages += '---> current.variables.group: ' + current.variables.group + '-' + identifier + '\n';

var userList = [];

if (users.indexOf(',') > -1) {

  userList = users.split(',');

}

else {

  userList.push(users + ''); // only one user found

}

// retrieve the list of user names

var userRecords = new GlideRecord('sys_user');

userRecords.addQuery('sys_id','IN',userList);

userRecords.query();

var userNames = [];

while(userRecords.next()) {

  userNames.push(userRecords.name + '');

}

// retrieve all the users already present in the group

var groupMembers = new GlideRecord('sys_user_grmember');

groupMembers.addQuery('group', group);

groupMembers.query();

// remove already existing members from the list

while(groupMembers.next()) {

  var member = groupMembers.user + '';

  if (userList.indexOf(member) > -1) {

  messages += '---> Member removed: ' + member + '\n';

  userList.splice(userList.indexOf(member),1); // remove the already existing member

  }

}

// retrieve group manager, and group name

var groupInfo = new GlideRecord('sys_user_group');

if (groupInfo.get('sys_id', group)) {

  workflow.scratchpad.manager = groupInfo.manager + '';

  workflow.scratchpad.groupName = groupInfo.name + '';

  messages += '---> workflow.scratchpad.groupName: ' + workflow.scratchpad.groupName + '-' + identifier + '\n';

  messages += '---> workflow.scratchpad.manager: ' + workflow.scratchpad.manager + '-' + identifier + '\n';

}

workflow.scratchpad.userNames = userNames;

workflow.scratchpad.group = group;

workflow.scratchpad.users = userList;

workflow.scratchpad.messages = messages;

c. Click the Submit button to save your activity.



5. Navigate to Core -> Conditions and place an If Activity after the Initialize Run Script Activity.

a. Name: Any users to process?

b. Advanced: Checked

c. Script:

answer = ifScript();

function ifScript() {

  if (workflow.scratchpad.users.length > 0) {

    return 'yes';

  }

  return 'no';

}

d. Click the Submit button to save your activity.



6. Place another If Activity after the Any Users to Process If Activity.

a. Name: Is Manager Present?

b. Advanced: Checked

c. Script:

answer = ifScript();

function ifScript() {

  if (JSUtil.notNil(workflow.scratchpad.manager)) {

  return 'yes';

  }

  return 'no';

}

7. Navigate to Core -> Approvals and place an Approval - User Activity after the If Activity.

  So here is our dynamic approval.   Simple, huh?   🙂   Remember requirement #4.   We only want to request approval from the group manager.

a. Name: Get Group Manager Approval

b. Advanced: Checked

c. Script:

var identifier = context.name + '.' + activity.name;

workflow.scratchpad.messages += '---> approval group: ' + workflow.scratchpad.group + '-' + identifier + '\n';

answer = [];

answer.push(workflow.scratchpad.manager);

d. Click the Submit button to save your activity.



8. Navigate to Core -> Utilities and pull out five Run Script Activities.

a. First activity:

i. Name: Log Users Already Exist

ii. Script:

workflow.scratchpad.messages += '---> Group (' + workflow.scratchpad.groupName + '): All users already exist in the group, or no users to process!   Process ending.\n';

iii. Click the Submit button to save your activity.

b. Second activity:

i. Name: Log Manager Not Present

ii. Script:

workflow.scratchpad.messages += '---> Group (' + workflow.scratchpad.groupName + '): The Group Manager was not found. The manager must be present! Process ending.\n';

iii. Click the Submit button to save your activity.

c. Third activity:

i. Name: Log Request Rejected

ii. Script:

workflow.scratchpad.messages += 'Users ('+ workflow.scratchpad.userNames +') rejected for group ---> '

  + workflow.scratchpad.groupName + '\n';

iii. Click the Submit button to save your activity.

d. Fourth activity:

i. Name: Add Users to Group

ii. Script:

addUsersToGroup(workflow.scratchpad.users, workflow.scratchpad.group);

function addUsersToGroup(userIDList, groupID) {

  for (var i=0; i < userIDList.length; i++) {

  workflow.scratchpad.messages += '\tUser (' + userIDList[i] + ') added to group: '

  + workflow.scratchpad.group + '\n';

  var groupMember = new GlideRecord("sys_user_grmember");

  groupMember.initialize();

  groupMember.user = userIDList[i] + '';

  groupMember.group = groupID + '';

  groupMember.insert();

  }

}

iii. Click the Submit button to save your activity.

e. Fifth activity:

i. Name: Log Messages

ii. Script:

var identifier = context.name + '.' + activity.name;

gs.log(workflow.scratchpad.messages, identifier);

iii. Click the Submit button to save your activity.


9. Navigate to Core -> Approvals and pull out an Approval Action Activity.

a. Name: Auto-Reject

b. Action: Mark task rejected.

10. Wire everything up to look like the following diagram:

find_real_file.png

NOTE:   The combination of the If and Timer activities make the workflow wait until the If condition is met.   In this case the variables are filled in.

11. Publish your workflow.   In Helsinki I found that sometimes my new changes were not picked up by Service Catalog unless I did this!   So if you find that modifications you made do not seem to appear during testing then this is the solution.



Lab 1.3: Create the Service Catalog Interface

1. Navigate to   Service Catalog -> Maintain Categories and create a new Category.

a. Title: User and Group Exercises

b. Catalog: Service Catalog

c. Right-click on the header and save your new category.

2. Add a new Catalog Item

                  NOTE: If you do not see some of the following fields on your form you may have to add them to your form.

a. Name: Add Users to Groups

b. Workflow: Add Users To Groups

c. Short description: Add a user to several groups

d. Use cart layout: unchecked

e. Omit price in cart: checked

f. No quantity: checked

g. No proceed to checkout: checked

h. No cart: checked.

i. Right-click on the header and save your new catalog item.

3. Add two variables to the new Catalog Item

a. Variable one

i. Type: List Collector

ii. Question: User(s)

iii. Name: users

iv. Mandatory: true

v. List table: User [sys_user]

vi. Reference Qualifier: active=true

vii. Order: 100

viii. Click on the Submit button to save your variable.

b. Variable two

i. Type: List Collector

ii. Question: Group(s)

iii. Name: groups

iv. Mandatory: true

v. List Table: Group [sys_user_group]

vi. Reference Qualifier: active=true

vii. Order: 200

viii. Click on the Submit button to save your variable.

Your Catalog Item should look something like this:

4. Add another new Catalog Item

a. Name: Add Users to Group

b. Workflow: Add Users To Group

c. Short description: Add users to group

d. Use cart layout: unchecked

e. Omit price in cart: checked

f. No quantity: checked

g. No proceed to checkout: checked

h. No cart: checked.

i. Right-click on the header and save your new catalog item.

5. Add two variables to the new Catalog Item

a. Variable one

i. Type: Reference

ii. Question: Group

iii. Name: group

iv. Mandatory: true

v. List table: Group [sys_user_group]

vi. Reference Qualifier: Simple

vii. Order: 100

viii. Click on the Submit button to save your variable.

b. Variable two

i. Type: List Collector

ii. Question: Users to add to group

iii. Name: users

iv. Mandatory: true

v. List Table: User [sys_user]

vi. Reference Qualifier: active=true

vii. Order: 200

viii. Click on the Submit button to save your variable.

Your Catalog Item should look something like this:

find_real_file.png

Lab 1.4: Testing

1. First we need to activate our new Category.   Navigate to Self-Service -> Service Catalog.

2. Click on the "+" button in the upper right corner to open the Sections form.

3. Add User and Group Exercises someplace on the form.

4. Click the "x" button to close the Sections form.

5. Now we can test our new Catalog Item.   Click on the User and Group Exercises link.

6. Click on the Add Users to Groups link.

Your Service Catalog Item form should look a bit like this:

7. Pick your favorite group, and a couple of users to add, and click the Order Now button.   The Order Status form will be displayed.  



8. Click on the first request number created (in my case REQ0010081).   The Request form will be displayed.  

9. Scroll to the bottom of the request form and note that there are three RITMs.   The first RITM is the main workflow (kicked off by this request), and the other two were generated by that workflow.

10. In the related list order the RITMs by Number ascending and click on the first (lowest numbered) record.   For me it would be RITM0010099.

11. Scroll to the Related Links and click on the Show Workflow link.   This will display the workflow context diagram.   It should look something like this:

12. Close this browser tab, and return to the Request Item tab.

13. Back arrow on your browser to get back to the Request form, scroll to the related links, and pick the second RITM record.

14. Scroll to the bottom of the RITM form.   You should see one or more approvers (depending on how many were already present in your group).

15. In related links for the RITM click on the Show Workflow link.   It should look something like this:

find_real_file.png

NOTE: By definition if there is no one already present in the group it will auto-approve.   So you may want to add at least one user to your group before you run this test.

16. Close this browser tab, and return to the Request Item tab.

17. From the Approvers tab, go ahead and approve one of the approvers.   You should see a couple of roles added messages appear at the top of the form.

18. Back arrow on your browser to get back to the Request form, scroll to the related links, and pick the second RITM record.   Look at the workflow (it should be the same as the other).   Approve this RITM as well.

19. Now look at that RITMs workflow again.   You will observe the completion of the flow.

find_real_file.png

20. Finally navigate to User Administration -> Groups, and open your group.   You should see all the newly added users in the Group Members tab.

Obviously, other tests you will want to perform are:

1. Do all users exist in the group?

2. Is the manager field filled in on the group?

3. Manager rejects the request

And you are done!

You will probably want to go the extra step of notifying the requestor of the results of each request, and you could also set up the status for the user to track where their request currently is.   Just a couple of cleanup items.   🙂

I want to highly recommend taking the ServiceNow Scripting training class should you get the opportunity.   The class has an entire module covering Workflow Scripting!

Steven Bell

accenture logo small.jpg

For a list of all of my articles:   Community Code Snippets: Articles List to Date

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

Also, if you are not already, I would like to encourage you to become a member of our blog!

21 Comments