It’s a good idea to have regular audits/reviews of COE security policies and HR groups to ensure that the permissions are setup as intended. This custom functionality provides a mechanism of performing audits/reviews with an HR case.
The basic idea of this functionality is
- An HR case is created to perform the audit
- HR Tasks to review group membership are created for the managers of each group that is contained in an COE Security Policy.
- HR Tasks to review the CEO Security Policies are created.
Here’s how to set it up:
HR Service
Create an HR Service called HRSD Audit in the HRIT Operations COE

Script Include
Create a script include HRSDAuditUtils as below
var HRSDAuditUtils = Class.create();
HRSDAuditUtils.prototype = {
initialize: function() {},
getHRSDGroupsInCOESecurityPolicies: function() {
//returns and array of sys_ids of groups used in active COE Security Policies
var groups = [];
var polgroupGR = new GlideRecord("sn_hr_core_m2m_security_policy_group");
polgroupGR.addEncodedQuery('security_policy.active=true');
polgroupGR.query();
while (polgroupGR.next()) {
var groupSysId = polgroupGR.group.sys_id.toString();
if (groups.indexOf(groupSysId) == -1) {
groups.push(groupSysId);
}
}
return groups;
},
getGroupMembersString: function(groupGR) {
//takes a group GlideRecord as input and returns the members of the group as a string with line breaks
var result = '';
var memberGR = new GlideRecord('sys_user_grmember');
memberGR.addQuery('group', groupGR.getUniqueValue());
memberGR.query();
while (memberGR.next()) {
result += memberGR.user.email.getDisplayValue() + '<br/>';
}
return result;
},
previewGroupsForMembershipAudit: function() {
//displays the groups that will be audited in an info message. Called from a UI Action
var groups = this.getHRSDGroupsInCOESecurityPolicies();
var htmlResult = 'Groups for membership audit: <br/>';
var groupGR = new GlideRecord('sys_user_group');
groupGR.addQuery('sys_id', 'IN', groups);
groupGR.orderBy('name');
groupGR.query();
while (groupGR.next()) {
htmlResult += groupGR.getDisplayValue() + ': ' + groupGR.manager.getDisplayValue() + '<br/>';
}
gs.addInfoMessage(htmlResult);
},
createAuditTaskforGroup: function(groupGR, parentCaseGR) {
var taskGR = new GlideRecord('sn_hr_core_task');
taskGR.initialize();
taskGR.setValue('template', '46e00eff87f6d210f7efc8460cbb354c'); //HRSD Audit Task Template
taskGR.setValue('short_description', 'Audit membership of group ' + groupGR.getDisplayValue());
taskGR.setValue('state', 10);
taskGR.setValue('hr_task_type', 'mark_when_complete');
taskGR.setValue('parent', parentCaseGR.getUniqueValue());
taskGR.setValue('assignment_group', parentCaseGR.getValue('assignment_group'));
var description = 'Members of group ' + groupGR.getDisplayValue() + ' as of ' + new GlideDateTime() + ' UTC <br/>';
description += this.getGroupMembersString(groupGR);
taskGR.setValue('rich_description', description);
taskGR.insert();
},
createGroupAuditTasks: function(parentCaseGR) {
//create the audit tasks for the active groups
var groups = this.getHRSDGroupsInCOESecurityPolicies();
var groupGR = new GlideRecord('sys_user_group');
groupGR.addQuery('sys_id', 'IN', groups);
groupGR.query();
while (groupGR.next()) {
var inputs = {};
inputs['group_gliderecord'] = groupGR; // GlideRecord of table: sys_user_group
inputs['case_gliderecord'] = parentCaseGR; // GlideRecord of table: sn_hr_core_case_operations
sn_fd.FlowAPI.getRunner().subflow('sn_hr_core.hrsd_audit__create_group_audit_task').inBackground().withInputs(inputs).run();
}
},
createCOESecurityPolicyAuditTasks: function(parentCaseGR) {
//Creates an audit task for each active COE Security Policy
var coePolicyGR = new GlideRecord("sn_hr_core_coe_security_policy");
coePolicyGR.addEncodedQuery("active=true");
coePolicyGR.query();
while (coePolicyGR.next()) {
var taskGR = new GlideRecord('sn_hr_core_task');
taskGR.initialize();
taskGR.setValue('template', '166367bb87bad21077df42e50cbb35c8'); //HRSD Audit COE Security
taskGR.setValue('short_description', 'HRSD Audit Task: Audit COE Sec Policy ' + coePolicyGR.policy_name.getDisplayValue());
taskGR.setValue('state', 10);
taskGR.setValue('hr_task_type', 'mark_when_complete');
taskGR.setValue('parent', parentCaseGR.getUniqueValue());
taskGR.setValue('assignment_group', parentCaseGR.getValue('assignment_group'));
taskGR.setValue('correlation_id', coePolicyGR.getUniqueValue());
var description = 'Go to the COE Security Policy record, verify the configuration and then click on Close Audit Task for this Policy related link <br/> <a href="/nav_to.do?uri=sn_hr_core_coe_security_policy.do?sys_id=' + coePolicyGR.getUniqueValue() + '" target="_blank">' + coePolicyGR.policy_name.getDisplayValue() + '</a>';
taskGR.setValue('rich_description', description);
taskGR.insert();
}
},
getCOESecurityPolicySnapshot: function(policyGR) {
//takes a snapshot of the current values of a given COE Security Policy
var snapshot = '';
snapshot += 'Policy: ' + policyGR.policy_name.getDisplayValue() + '\n';
snapshot += 'Snapshot Date: ' + new GlideDateTime() + ' UTC \n';
snapshot += 'COE: ' + policyGR.coe.getDisplayValue() + '\n';
snapshot += 'Applies to all child COEs: ' + policyGR.applies_to_all_child_coes.getDisplayValue() + '\n';
snapshot += 'Applies to all services: ' + policyGR.all_services.getDisplayValue() + '\n';
snapshot += 'Services: ' + policyGR.services.getDisplayValue() + '\n';
snapshot += 'Short description: ' + policyGR.short_description.getDisplayValue() + '\n';
snapshot += 'Applies when ' + policyGR.applies_when.getDisplayValue() + '\n';
snapshot += 'Groups: \n';
var groupsGR = new GlideRecord('sn_hr_core_m2m_security_policy_group');
groupsGR.addQuery('security_policy', policyGR.getUniqueValue());
groupsGR.query();
while (groupsGR.next()) {
snapshot += ' ' + groupsGR.group.name.getDisplayValue() + '\n';
}
return snapshot;
},
closeCOESecurityPolicyAuditTask: function(policyGR) {
//takes a snapshot and closes any open audit tasks for the given COE security policy
var hrTaskGR = new GlideRecord('sn_hr_core_task');
hrTaskGR.addQuery('correlation_id', policyGR.getUniqueValue());
hrTaskGR.addActiveQuery();
hrTaskGR.query();
gs.addInfoMessage('Open Audit Tasks to be closed: ' + hrTaskGR.getRowCount());
while (hrTaskGR.next()) {
var snapshot = this.getCOESecurityPolicySnapshot(policyGR);
var currentNotes = hrTaskGR.close_notes.getDisplayValue() + '\n';
currentNotes += '------------------------------------\n';
currentNotes += 'Snapshot of COE Sec Policy \n';
currentNotes += '------------------------------------\n';
hrTaskGR.setValue('close_notes', currentNotes + snapshot);
hrTaskGR.setValue('u_audit_task_result', 'pass');
hrTaskGR.setValue('state', 3);
hrTaskGR.update();
}
},
type: 'HRSDAuditUtils'
};
HR Templates
Create HR Templates
- HRSD Audit for on the HRIT Operations table for the case
- HRSD Group Membership Audit Task on the HR Task Table
- HRSD Audit Change Task on the HR Table
HR Task Table
Create a choice field called Audit Result with possible values of Pass and Fail.
Employee Form
Create an Employee Form to collect the group member confirmation from group managers.

The first question is a choice field with Yes and No as answers. The second question is a string field.

Subflow
Create a Flow which creates the HR Task for Collect Employee Input and then retrieves the results. See this article on retrieving data from Employee Forms https://incident.do/2024/11/08/servicenow-hrsd-updating-hr-tasks-or-hr-cases-with-employee-forms/
The inputs are
- The gliderecord of the audit case (HRIT Operations)
- The gliderecord of the group for which membership should be verified.

Step 1

short description script
return 'Confirm membership of group ' + fd_data.subflow_inputs.group_gliderecord.getDisplayValue();
description script
var html = 'Please confirm that the members of the group ' + fd_data.subflow_inputs.group_gliderecord.getDisplayValue() + ' are correct (members as of ' + new GlideDateTime() + ' UTC )<br/><br/>';
html += new HRSDAuditUtils().getGroupMembersString(fd_data.subflow_inputs.group_gliderecord);
return html;
Step 2

Step 3 and 4 – If the manager confirmed the members of the group, update the HR Task Audit Result field to Pass


Step 5 and 6 – If the manager requested changes to the group membership then update the HR Task Audit Result to Fail


Step 7 and 8 – Get the string entered by the manager on the Employee Form and create a new HR Task which contains the string


Store the group’s sys_id in the Correlation ID field of the new HR Task so that we can send another group membership audit task for the group after we made the modifications.

UI Actions
Preview Groups for Membership Audit
Create a UI Action to preview the groups contained in COE Security Policies. This allows the person working the audit case to look at the groups and the managers before sending the group membership audit tasks

action.setRedirectURL(current);
new HRSDAuditUtils().previewGroupsForMembershipAudit();
Create Group Membership Audit Tasks
Create another UI Action to call the Subflow created above

action.setRedirectURL(current);
new HRSDAuditUtils().createGroupAuditTasks(current);
gs.addInfoMessage('HRSD Group Audit Tasks created. Refresh the page to see all tasks');
Create HR Grp Membership Audit Task
This UI Action is on the HR Task table, it is used when a manager requests changes to group membership. Once the changes are complete, this UI Action creates another Group Membership Audit Task for the manager to complete.

//launches the Create HRSD Audit Task subflow
action.setRedirectURL(current);
var groupGR = new GlideRecord('sys_user_group');
groupGR.get(current.getValue('correlation_id')); //group sys_id is stored in the correlation_id field.
var caseGR = new GlideRecord('sn_hr_core_case_operations');
caseGR.get(current.getValue('parent'));
if (groupGR.isValidRecord() && caseGR.isValidRecord()) {
createTask();
}
function createTask() {
gs.addInfoMessage('A new HRSD Audit Task will be created for ' + groupGR.getDisplayValue());
var inputs = {};
inputs['group_gliderecord'] = groupGR; // GlideRecord of table: sys_user_group
inputs['case_gliderecord'] = caseGR; // GlideRecord of table: sn_hr_core_case_operations
// Execute Synchronously: Run in foreground. Code snippet has access to outputs.
sn_fd.FlowAPI.getRunner().subflow('sn_hr_core.hrsd_audit__create_group_audit_task').inForeground().withInputs(inputs).run();
}
Create COE Security Policy Audit Tasks
This UI Policy creates an audit task for each active COE Security Policy

action.setRedirectURL(current);
new HRSDAuditUtils().createCOESecurityPolicyAuditTasks(current);
gs.addInfoMessage('HRSD COE Security Policy Audit Tasks created');
Close Audit Task for this Policy
This is an action on the COE Security Policy table to close the audit task. It takes a snapshot of the current configuration of the policy and writes it to the Close Notes of the HR Task.

action.setRedirectURL(current);
new HRSDAuditUtils().closeCOESecurityPolicyAuditTask(current);
