Scripted Audit Creating the Same Task Multiple Times

Von Naval
Tera Contributor

Hello all. I have a scripted audit where it records service offerings for compliance, and in the code below, it checks whether the Business Contact field of the TSO is valid and if the user is active.

One of my requirements is to create a task where if the audit result is failed, I am to create a task for that audit.

I've added the following code before the certification processing of the audit:

var followOnTask = new SNC.CertificationProcessing().createFollowOnTask(current.sys_id, sysId, assignToUser, '', taskMsg);

but the problem is that this code would generate the same task every time the audit has been ran.

Is there a way I could manipulate the code to make it so that they won't have to create a duplicate task when it is already generated and still in open state?

 

I've tried one of the system properties that the documentation mentioned to set to false to prohibit the creation of the same task, but it didn't work.

 

This is just a part of the scripted audit for reference.

var certProcessor;
if (gs.getCurrentScopeName().includes("global"))
	certProcessor = new SNC.CertificationProcessing();
else {
	certProcessor = new CertificationProcessing();
}

// API call to retrieve records based on the filter
var gr = new SNC.CertificationProcessing().getFilterRecords(current.filter);

// Loop over all records defined by the filter
while (gr.next()) {
	var pass = true;
	var columnNameSpace = '';
	var hasAvailability = false;
	var userGr = new GlideRecord('sys_user');
	var soCommitmentGr = new GlideRecord('service_offering_commitment');

	var sysId = gr.getValue('sys_id'); // Sys ID of audited record
	var businessContact = gr.getValue('business_contact'); //is not null or blank and user is active
	var serviceOwner = gr.getValue('owned_by'); //is not null or blank and user is active
	var serviceManager = gr.getValue('managed_by'); //is not null or blank and user is active
	var deliveryOwner = gr.getValue('delivery_manager'); //is not null or blank and user is active
	var primaryContact = gr.getValue('supported_by'); //is not null or blank and user is active
	var primarySPG = gr.getValue('support_group'); //is not null or blank 
	var platformName = gr.getValue('u_es_organisational_platform'); //is not null or blank
	var description = gr.getValue('description'); //at least 15 characters


	var approvalGroup = gr.getValue('change_control'); //is not null or blank
	var environment = gr.getValue('environment'); //is not null or blank
	var arcAvailabilityTier = gr.getValue('u_cba_availability_tier'); //is not null or blank
	var arcRecoverabilityTier = gr.getValue('u_cba_arc_recoverability_tier'); //is not null or blank
	var arcCyberSecurityTier = gr.getValue('u_cba_cyber_security_tier'); //is not null or blank
	var arcOverallTier = gr.getValue('u_cba_overall_tier'); //is not null or blank
	var costcenter = gr.getValue('cost_center'); //is not null or blank
	var company = gr.getValue('company'); //is not null or blank
	var availability = ''; //Service comitment record exists of type 'Availability' and is not empty





	// Determine if businessContact not null or blank and user is active
	if (!businessContact) {
		columnNameSpace = gr.business_contact.getLabel(); // String value of column audited against
		// Call log failed result API
		// Params:
		// auditId - Sys id of audit record executed
		// auditedRecordId - Sys id of the record audited
		// followOnTask - Sys id of the follow on task associated with the audited record(@see auditedRecordId). Can be empty
		// columnDisplayName - Label of the column audited(ex. Disk space (GB)).  Can be empty
		// operatorLabel - Label of the operator used to audit the column(ex. is not empty, greater than). Can be empty
		// desiredValue - Desired value of the column.  Can be empty
		// discrepancyValue - Discrepancy value.  Can be empty
		// isCI - True, if audited record is a CI. False, otherwise.
		// domainToUse - Sys domain of the "cert_audit" record.  Can be empty
		new SNC.CertificationProcessing().logAuditResultFail(current.sys_id, sysId, '', columnNameSpace, 'not null', '', gr.getDisplayValue('business_contact'), true);
		pass = false;
	} else {
		//Check user is active
		userGr = new GlideRecord('sys_user');
		userGr.get(businessContact);
		if (userGr.isValidRecord()) {
			if (userGr.getValue('active') == 0) {
				columnNameSpace = gr.business_contact.getLabel(); // String value of column audited against
				new SNC.CertificationProcessing().logAuditResultFail(current.sys_id, sysId, '', columnNameSpace, 'active user', '', gr.getDisplayValue('business_contact'), true);
				pass = false;
			}
		}
	}

 

Any help is much appreciated!

4 REPLIES 4

CMDB Whisperer
Mega Sage
Mega Sage

It's a great question, and one worth pursuing a good answer for, IMO.  I have run into this before and basically come up with the answer that I'd have to write a lot of potentially unpleasant code for this or create a more enhanced auditing framework on top of something that is already a bit convoluted in some ways, and have landed on this: if you want methods to track health on a more frequent basis than you are willing to deal with the creation of new, possibly duplicate audit tasks, you might be better off handling this through other means than audits.  And if you're going to be actually creating tasks for audits, those tasks should have some weight to them.  They should have SLAs and governance, and reporting to show whether they are being addressed.  They shouldn't be just another way to look at how we're doing with our data completeness/correctness.  And if looking at it like that makes you (or your customers!) cringe at the thought of having all of these tasks land in their plate "on top of all the other work they have to do" maybe also consider narrowing the criteria of your audit so that at least you are only selecting the "top offenders" or most critical business impact areas as a target for your scripted audits.


The opinions expressed here are the opinions of the author, and are not endorsed by ServiceNow or any other employer, company, or entity.

Another thing you could consider doing is to assign these tasks to a CMDB Remediation Workflow that will modify your data automatically (e.g. walk up the management hierarchy as managers may be able to assume accountability for assigning responsibility for previously active direct reports) and then complement the auto-reassignment of those roles with notifications: "ACTION REQUIRED: You have been assigned the following things for the following reasons... make sure these items are properly assigned to the right people"

 

That said, based on a quick review of your code, I don't know whether getValue('active') == 0 is actually the right logical test for that condition.  I could be wrong though.  And also the !businessContact test looks suspect, because getValue() might not return an actual null, but rather an empty string.  I would use gs.nil(businessContact) for this test instead to be sure.


The opinions expressed here are the opinions of the author, and are not endorsed by ServiceNow or any other employer, company, or entity.

Thanks so much for your inputs! I do agree on all of your inputs to be honest. What is the actual best practice to tackle this sort of solution?

Truth be told, we have about 2,000 CIs that are detected by this audit of ours and from each errors per CI, a task has to be generated, which would probably bloat the instance with information. 

ishan_
Tera Contributor