From 2da9254ab1a15fbaf6851c7a84f9b46c81711023 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 01:17:01 +0530 Subject: [PATCH 01/30] script.js Automatically create a problem record from incident volume Use case: Automatically create a problem record if a specific Configuration Item (CI) is associated with more than a certain number of incidents within a defined timeframe. Code snippet This code can be placed in a Scheduled Script Execution or an After Insert Business Rule to check new incidents --- .../script.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js diff --git a/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js b/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js new file mode 100644 index 0000000000..dcec063918 --- /dev/null +++ b/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js @@ -0,0 +1,13 @@ +var incidentCheck = new GlideAggregate('incident'); + incidentCheck.addQuery('cmdb_ci', current.cmdb_ci); // Or any specific CI + incidentCheck.addQuery('opened_at', '>', 'javascript:gs.minutesAgoStart(60)'); + incidentCheck.addAggregate('COUNT'); + incidentCheck.query(); + + if (incidentCheck.next() && parseInt(incidentCheck.getAggregate('COUNT')) > 5) { + var problem = new GlideRecord('problem'); + problem.initialize(); + problem.short_description = 'Multiple incidents reported for CI: ' + current.cmdb_ci.getDisplayValue(); + problem.cmdb_ci = current.cmdb_ci; + problem.insert(); + } From c9315ec134f33bba9be4a53522c89b6c36965d1d Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 01:18:16 +0530 Subject: [PATCH 02/30] README.md --- .../README.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md diff --git a/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md b/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md new file mode 100644 index 0000000000..6f7afc5eac --- /dev/null +++ b/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md @@ -0,0 +1,37 @@ +Key features +Automatic problem creation: The script uses a GlideAggregate query to count the number of incidents opened for a specific CI. +Time-based threshold: Problems are only created if more than five incidents are opened within a 60-minute window. +Targeted incidents: The script focuses on incidents related to the same CI, making it effective for identifying recurring infrastructure issues. +Customizable conditions: The number of incidents and the time frame are easily configurable within the script. +Efficient performance: The use of GlideAggregate ensures the database is queried efficiently, minimizing performance impact. + +How it works +The script is designed to be executed as a server-side script, typically within a Business Rule. When an incident is inserted or updated, the script performs the following actions: +Queries incidents: It executes a GlideAggregate query on the incident table. +Sets conditions: The query is filtered to count all incidents that meet the following conditions: +Same CI: The incident's cmdb_ci matches the cmdb_ci of the current record. +Within the last hour: The opened_at time is within the last 60 minutes. +Evaluates count: After the query is run, the script checks if the count of matching incidents exceeds the threshold (in this case, 5). +Creates problem: If the threshold is exceeded, a new problem record is initialized. +The short_description is automatically populated with a descriptive message. +The cmdb_ci is linked to the new problem record. +The new record is then inserted into the database. +Implementation steps +Create a Business Rule: +Navigate to System Definition > Business Rules. +Click New. +Configure the Business Rule: +Name: Auto Create Problem from Multiple Incidents +Table: Incident [incident] +Advanced: true +When to run: Select after and check the Insert and Update checkboxes. This ensures the script runs after an incident has been saved. +Filter conditions: Optionally, you can add conditions to limit when the script runs (e.g., cmdb_ci is not empty). +Add the script: +Navigate to the Advanced tab. +Copy and paste the script into the Script field. +Customize (optional): +Number of incidents: Change the > 5 value to adjust the threshold. +Time frame: Adjust the gs.minutesAgoStart(60) value to change the time window. +Other conditions: If you need to check for specific incident statuses or categories, add more addQuery lines to the GlideAggregate call. +Save the Business Rule. +Customization examples From 5d17de8e7e9d72872248c773dba953dce931a82a Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 01:30:06 +0530 Subject: [PATCH 03/30] Script.js Identify the oldest active incident for each assignment group. This helps managers focus on long-running tickets that may require special attention. --- .../script.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js diff --git a/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js b/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js new file mode 100644 index 0000000000..4aabd461b1 --- /dev/null +++ b/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js @@ -0,0 +1,11 @@ +var ga = new GlideAggregate('incident'); + ga.addActiveQuery(); + ga.addAggregate('MIN', 'opened_at'); + ga.groupBy('assignment_group'); + ga.query(); + + while (ga.next()) { + var group = ga.assignment_group.getDisplayValue(); + var oldestIncidentDate = ga.getAggregate('MIN', 'opened_at'); + gs.info("Oldest open incident for " + group + " was created on: " + oldestIncidentDate); + } From c8c2e0642d42fc32b0393416020c9305fac204cd Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 01:32:57 +0530 Subject: [PATCH 04/30] README.md --- .../README.md | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md diff --git a/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md b/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md new file mode 100644 index 0000000000..722a45ac02 --- /dev/null +++ b/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md @@ -0,0 +1,92 @@ +ServiceNow Script: Find Oldest Open Incidents per Group +This script leverages GlideAggregate to efficiently find the oldest active incident for each assignment group. This is a powerful tool for monitoring and reporting on potential service level agreement (SLA) risks and improving incident management processes. +Overview +The script performs the following actions: +Initializes GlideAggregate: Creates an aggregate query on the incident table. +Filters Active Incidents: Uses addActiveQuery() to restrict the search to only open (active) incidents. +Aggregates by Minimum Date: Finds the minimum (MIN) opened_at date, which represents the oldest record. +Groups by Assignment Group: Groups the results by the assignment_group to get a separate result for each team. +Iterates and Logs: Loops through the query results and logs the assignment group and the opening date of its oldest open incident. +How to use +This script is intended to be used in a server-side context within a ServiceNow instance. Common use cases include: +Scheduled Job: Run this script on a regular schedule (e.g., daily) to generate a report on aging incidents. +Script Include: Incorporate the logic into a reusable function within a Script Include, allowing other scripts to call it. +Fix Script: Execute the script as a one-off task to perform an analysis. +Script +javascript +(function findOldestIncidents() { + var ga = new GlideAggregate('incident'); + ga.addActiveQuery(); + ga.addAggregate('MIN', 'opened_at'); + ga.groupBy('assignment_group'); + ga.query(); + + while (ga.next()) { + var group = ga.assignment_group.getDisplayValue(); + var oldestIncidentDate = ga.getAggregate('MIN', 'opened_at'); + gs.info("Oldest open incident for " + group + " was created on: " + oldestIncidentDate); + } +})(); +Use code with caution. + +Installation +As a Scheduled Job +Navigate to System Definition > Scheduled Jobs. +Click New and select Automatically run a script of your choosing. +Name the job (e.g., Find Oldest Open Incidents). +Set your desired frequency and time. +Paste the script into the Run this script field. +Save and activate the job. +As a Script Include +Navigate to System Definition > Script Includes. +Click New. +Name it (e.g., IncidentHelper). +API Name: global.IncidentHelper +Script: +javascript +var IncidentHelper = Class.create(); +IncidentHelper.prototype = { + initialize: function() { + }, + + findOldestIncidents: function() { + var ga = new GlideAggregate('incident'); + ga.addActiveQuery(); + ga.addAggregate('MIN', 'opened_at'); + ga.groupBy('assignment_group'); + ga.query(); + + var results = []; + while (ga.next()) { + var group = ga.assignment_group.getDisplayValue(); + var oldestIncidentDate = ga.getAggregate('MIN', 'opened_at'); + results.push({ + group: group, + oldestIncidentDate: oldestIncidentDate + }); + } + return results; + }, + + type: 'IncidentHelper' +}; +Use code with caution. + +You can then call this function from other scripts. For example: +javascript +var helper = new IncidentHelper(); +var incidents = helper.findOldestIncidents(); +for (var i = 0; i < incidents.length; i++) { + gs.info("Oldest open incident for " + incidents[i].group + " was created on: " + incidents[i].oldestIncidentDate); +} +Use code with caution. + +Customization +Change the output: Modify the gs.info() line to instead write to a custom log, send an email, or create a report. +Refine the query: Add more addQuery() statements to filter incidents by other criteria, such as priority or category. +Change the aggregate: Use MAX instead of MIN to find the newest incident in each group. +Get incident details: To get the actual incident record (e.g., its number), you would need to perform a secondary GlideRecord query based on the aggregated data. +Dependencies +This script uses standard ServiceNow APIs (GlideAggregate, gs). No external libraries are required. +Support +For any questions or issues, please open an issue in this repository. From 14244d8b6541501ee7a8ae74854231812e3550a7 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 01:47:20 +0530 Subject: [PATCH 05/30] script.js This example searches a JSON document for all developers listed under the specified path. --- .../GlideJsonPath/GlideJsonPath Reader Example/script.js | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js diff --git a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js new file mode 100644 index 0000000000..ddfd540a46 --- /dev/null +++ b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js @@ -0,0 +1,2 @@ +var v = new GlideJsonPath('{"lib":{"jsonpath":{"cricket":{"name":"India","players":["Rohit","Dhoni","Kholi"]}}}}'); +var l = v.read("$['lib']['jsonpath']['cricket']['players'][*]"); From 45c645dde25e769504a7857ba941cc06d67d3d8c Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 01:51:09 +0530 Subject: [PATCH 06/30] README.md --- .../GlideJsonPath Reader Example/README.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md diff --git a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md new file mode 100644 index 0000000000..1b05e5aa11 --- /dev/null +++ b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md @@ -0,0 +1,42 @@ +Parse JSON with GlideJsonPath +This script demonstrates how to use the GlideJsonPath API in ServiceNow to parse a complex JSON string and extract specific data. In this example, it efficiently retrieves an array of player names from a nested JSON structure representing cricket team information. +Overview +The script performs the following actions: +Initializes GlideJsonPath: A new instance of GlideJsonPath is created, passing the target JSON string as a parameter. +Reads data with JSONPath: The read() method is called with a JSONPath expression to extract the desired data. The expression "$['lib']['jsonpath']['cricket']['players'][*]" navigates to the array of players: +$ represents the root of the JSON object. +['lib'], ['jsonpath'], and ['cricket'] use bracket notation to traverse the nested object structure. +['players'] accesses the array of player names. +[*] is a wildcard that returns all elements in the players array. +Stores the result: The extracted array of player names is stored in the l variable. +Use case +This script is useful in scenarios where you need to parse JSON data returned from a REST API call or an integration. By using GlideJsonPath, you can quickly and efficiently extract specific pieces of information without writing complex code to traverse the entire JSON object manually. +How to use +This script is intended for use in a server-side context within ServiceNow, such as a Script Include or Business Rule. +Example: Script Include +javascript +var CricketJsonParser = Class.create(); +CricketJsonParser.prototype = { + initialize: function() { + }, + + getPlayers: function() { + var jsonString = '{"lib":{"jsonpath":{"cricket":{"name":"India","players":["Rohit","Dhoni","Kholi"]}}}}'; + var v = new GlideJsonPath(jsonString); + var playerNames = v.read("$['lib']['jsonpath']['cricket']['players'][*]"); + + // At this point, `playerNames` is a JavaObject array. + // You can log it or process it further. + gs.info("Extracted players: " + JSON.stringify(playerNames)); + + return playerNames; + }, + + type: 'CricketJsonParser' +}; +Use code with caution. + +Customization +JSON Data: Replace the hardcoded jsonString with the variable that holds your JSON data (e.g., the body of a REST response). +JSONPath Expression: Modify the JSONPath expression in the read() method to extract different data from your JSON structure. +Result Handling: The read() function returns a JavaObject array, not a standard JavaScript array. If you need to use JavaScript array methods (like forEach), you may need to convert it. From 3edf0bcc88e628349a36d23e68c7cb30efb057e9 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 01:53:44 +0530 Subject: [PATCH 07/30] Update README.md --- .../GlideJsonPath/GlideJsonPath Reader Example/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md index 1b05e5aa11..8106a2c022 100644 --- a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md +++ b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md @@ -1,5 +1,6 @@ Parse JSON with GlideJsonPath This script demonstrates how to use the GlideJsonPath API in ServiceNow to parse a complex JSON string and extract specific data. In this example, it efficiently retrieves an array of player names from a nested JSON structure representing cricket team information. + Overview The script performs the following actions: Initializes GlideJsonPath: A new instance of GlideJsonPath is created, passing the target JSON string as a parameter. From fa78c36b29ca1af659fe6c8a2d8406b2b3ff63b5 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 02:01:43 +0530 Subject: [PATCH 08/30] script.js Identify inactive users who still have unresolved incidents. This helps with offboarding processes and ensures incidents aren't left unattended. --- .../script.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js diff --git a/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js b/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js new file mode 100644 index 0000000000..3699d502ce --- /dev/null +++ b/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js @@ -0,0 +1,11 @@ +var ga = new GlideAggregate('incident'); + ga.addQuery('active', true); + ga.addQuery('assigned_to.active', false); + ga.addAggregate('COUNT', 'assigned_to'); + ga.query(); + + while (ga.next()) { + var inactiveUser = ga.assigned_to.getDisplayValue(); + var taskCount = ga.getAggregate('COUNT', 'assigned_to'); + gs.info("Inactive user " + inactiveUser + " has " + IncidentCount + " active tasks."); + } From a6e23bd58ca5f781dffb723dd0d71840d90ec8e0 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 02:03:16 +0530 Subject: [PATCH 09/30] README.md --- .../README.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md diff --git a/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md b/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md new file mode 100644 index 0000000000..a51d9e2ead --- /dev/null +++ b/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md @@ -0,0 +1,46 @@ +Count Active Incidents Assigned to Inactive Users +This script uses GlideAggregate to efficiently count the number of active incidents assigned to inactive users. This is a crucial task for maintaining data hygiene and preventing incidents from being stalled due to inactive assignees. +Overview +The script performs the following actions: +Initializes GlideAggregate: Creates an aggregate query on the incident table. +Filters Records: Uses addQuery() to restrict the search to incidents that are both active (true) and assigned to a user whose active status is false. This filter uses a "dot-walk" on the assigned_to field to check the user's active status directly within the query. +Aggregates by Count: Uses addAggregate() to count the number of incidents, grouping the results by assigned_to user. +Executes and Logs: Runs the query, then loops through the results. For each inactive user found, it logs their name and the number of active incidents assigned to them. +Use case +This script is essential for regular cleanup and maintenance. It can be used in: +Scheduled Job: Automatically run the script daily or weekly to monitor for and report on incidents assigned to inactive users. +Fix Script: Perform a one-off run to identify and log all current incidents that need reassignment. +Reporting: The output from this script can be used to build a report for managers to track and reassign incidents promptly. +Script +javascript +var ga = new GlideAggregate('incident'); +ga.addQuery('active', true); +ga.addQuery('assigned_to.active', false); +ga.addAggregate('COUNT', 'assigned_to'); +ga.query(); + +while (ga.next()) { + var inactiveUser = ga.assigned_to.getDisplayValue(); + var taskCount = ga.getAggregate('COUNT', 'assigned_to'); + gs.info("Inactive user " + inactiveUser + " has " + taskCount + " active incidents."); +} +Use code with caution. + +Installation +As a Scheduled Job +Navigate to System Definition > Scheduled Jobs. +Click New and select Automatically run a script of your choosing. +Name the job (e.g., Find Incidents Assigned to Inactive Users). +Set your desired frequency and time. +Paste the script into the Run this script field. +Save and activate the job. +As a Fix Script +Navigate to System Definition > Fix Scripts. +Click New. +Name it (e.g., Find Active Incidents with Inactive Assignee). +Paste the script into the Script field. +Run the script to see the results in the System Log. +Customization +Targeted tables: Change the table name from incident to task or any other table with an assigned_to field to check for active records assigned to inactive users. +Automated reassignment: Extend the script to automatically reassign the incidents to a group or another user. This is a common practice to ensure that tickets do not get stuck in the queue. +Email notification: Instead of logging the information, modify the script to send an email notification to the group manager or another stakeholder with the list of incidents needing attention. From 484bcdda3d6c1ebf13870430d9aabd456a2ea3bf Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 02:08:11 +0530 Subject: [PATCH 10/30] Update script.js --- .../GlideJsonPath/GlideJsonPath Reader Example/script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js index ddfd540a46..063812aad7 100644 --- a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js +++ b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js @@ -1,2 +1,3 @@ var v = new GlideJsonPath('{"lib":{"jsonpath":{"cricket":{"name":"India","players":["Rohit","Dhoni","Kholi"]}}}}'); var l = v.read("$['lib']['jsonpath']['cricket']['players'][*]"); + From 12f9ba99ae96f9b0877442212b7e43af784f2e5c Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 09:40:15 +0530 Subject: [PATCH 11/30] Delete Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md --- .../README.md | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md diff --git a/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md b/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md deleted file mode 100644 index a51d9e2ead..0000000000 --- a/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md +++ /dev/null @@ -1,46 +0,0 @@ -Count Active Incidents Assigned to Inactive Users -This script uses GlideAggregate to efficiently count the number of active incidents assigned to inactive users. This is a crucial task for maintaining data hygiene and preventing incidents from being stalled due to inactive assignees. -Overview -The script performs the following actions: -Initializes GlideAggregate: Creates an aggregate query on the incident table. -Filters Records: Uses addQuery() to restrict the search to incidents that are both active (true) and assigned to a user whose active status is false. This filter uses a "dot-walk" on the assigned_to field to check the user's active status directly within the query. -Aggregates by Count: Uses addAggregate() to count the number of incidents, grouping the results by assigned_to user. -Executes and Logs: Runs the query, then loops through the results. For each inactive user found, it logs their name and the number of active incidents assigned to them. -Use case -This script is essential for regular cleanup and maintenance. It can be used in: -Scheduled Job: Automatically run the script daily or weekly to monitor for and report on incidents assigned to inactive users. -Fix Script: Perform a one-off run to identify and log all current incidents that need reassignment. -Reporting: The output from this script can be used to build a report for managers to track and reassign incidents promptly. -Script -javascript -var ga = new GlideAggregate('incident'); -ga.addQuery('active', true); -ga.addQuery('assigned_to.active', false); -ga.addAggregate('COUNT', 'assigned_to'); -ga.query(); - -while (ga.next()) { - var inactiveUser = ga.assigned_to.getDisplayValue(); - var taskCount = ga.getAggregate('COUNT', 'assigned_to'); - gs.info("Inactive user " + inactiveUser + " has " + taskCount + " active incidents."); -} -Use code with caution. - -Installation -As a Scheduled Job -Navigate to System Definition > Scheduled Jobs. -Click New and select Automatically run a script of your choosing. -Name the job (e.g., Find Incidents Assigned to Inactive Users). -Set your desired frequency and time. -Paste the script into the Run this script field. -Save and activate the job. -As a Fix Script -Navigate to System Definition > Fix Scripts. -Click New. -Name it (e.g., Find Active Incidents with Inactive Assignee). -Paste the script into the Script field. -Run the script to see the results in the System Log. -Customization -Targeted tables: Change the table name from incident to task or any other table with an assigned_to field to check for active records assigned to inactive users. -Automated reassignment: Extend the script to automatically reassign the incidents to a group or another user. This is a common practice to ensure that tickets do not get stuck in the queue. -Email notification: Instead of logging the information, modify the script to send an email notification to the group manager or another stakeholder with the list of incidents needing attention. From 23f8bb31260f98bbf604ccc5109e9fb3f9fe96a5 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 09:40:42 +0530 Subject: [PATCH 12/30] Delete Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js --- .../script.js | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js diff --git a/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js b/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js deleted file mode 100644 index 3699d502ce..0000000000 --- a/Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js +++ /dev/null @@ -1,11 +0,0 @@ -var ga = new GlideAggregate('incident'); - ga.addQuery('active', true); - ga.addQuery('assigned_to.active', false); - ga.addAggregate('COUNT', 'assigned_to'); - ga.query(); - - while (ga.next()) { - var inactiveUser = ga.assigned_to.getDisplayValue(); - var taskCount = ga.getAggregate('COUNT', 'assigned_to'); - gs.info("Inactive user " + inactiveUser + " has " + IncidentCount + " active tasks."); - } From 75d8c0b78e00dfe7d871c15dd159e6fbf6798b79 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 09:41:46 +0530 Subject: [PATCH 13/30] Delete Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md --- .../README.md | 92 ------------------- 1 file changed, 92 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md diff --git a/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md b/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md deleted file mode 100644 index 722a45ac02..0000000000 --- a/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/README.md +++ /dev/null @@ -1,92 +0,0 @@ -ServiceNow Script: Find Oldest Open Incidents per Group -This script leverages GlideAggregate to efficiently find the oldest active incident for each assignment group. This is a powerful tool for monitoring and reporting on potential service level agreement (SLA) risks and improving incident management processes. -Overview -The script performs the following actions: -Initializes GlideAggregate: Creates an aggregate query on the incident table. -Filters Active Incidents: Uses addActiveQuery() to restrict the search to only open (active) incidents. -Aggregates by Minimum Date: Finds the minimum (MIN) opened_at date, which represents the oldest record. -Groups by Assignment Group: Groups the results by the assignment_group to get a separate result for each team. -Iterates and Logs: Loops through the query results and logs the assignment group and the opening date of its oldest open incident. -How to use -This script is intended to be used in a server-side context within a ServiceNow instance. Common use cases include: -Scheduled Job: Run this script on a regular schedule (e.g., daily) to generate a report on aging incidents. -Script Include: Incorporate the logic into a reusable function within a Script Include, allowing other scripts to call it. -Fix Script: Execute the script as a one-off task to perform an analysis. -Script -javascript -(function findOldestIncidents() { - var ga = new GlideAggregate('incident'); - ga.addActiveQuery(); - ga.addAggregate('MIN', 'opened_at'); - ga.groupBy('assignment_group'); - ga.query(); - - while (ga.next()) { - var group = ga.assignment_group.getDisplayValue(); - var oldestIncidentDate = ga.getAggregate('MIN', 'opened_at'); - gs.info("Oldest open incident for " + group + " was created on: " + oldestIncidentDate); - } -})(); -Use code with caution. - -Installation -As a Scheduled Job -Navigate to System Definition > Scheduled Jobs. -Click New and select Automatically run a script of your choosing. -Name the job (e.g., Find Oldest Open Incidents). -Set your desired frequency and time. -Paste the script into the Run this script field. -Save and activate the job. -As a Script Include -Navigate to System Definition > Script Includes. -Click New. -Name it (e.g., IncidentHelper). -API Name: global.IncidentHelper -Script: -javascript -var IncidentHelper = Class.create(); -IncidentHelper.prototype = { - initialize: function() { - }, - - findOldestIncidents: function() { - var ga = new GlideAggregate('incident'); - ga.addActiveQuery(); - ga.addAggregate('MIN', 'opened_at'); - ga.groupBy('assignment_group'); - ga.query(); - - var results = []; - while (ga.next()) { - var group = ga.assignment_group.getDisplayValue(); - var oldestIncidentDate = ga.getAggregate('MIN', 'opened_at'); - results.push({ - group: group, - oldestIncidentDate: oldestIncidentDate - }); - } - return results; - }, - - type: 'IncidentHelper' -}; -Use code with caution. - -You can then call this function from other scripts. For example: -javascript -var helper = new IncidentHelper(); -var incidents = helper.findOldestIncidents(); -for (var i = 0; i < incidents.length; i++) { - gs.info("Oldest open incident for " + incidents[i].group + " was created on: " + incidents[i].oldestIncidentDate); -} -Use code with caution. - -Customization -Change the output: Modify the gs.info() line to instead write to a custom log, send an email, or create a report. -Refine the query: Add more addQuery() statements to filter incidents by other criteria, such as priority or category. -Change the aggregate: Use MAX instead of MIN to find the newest incident in each group. -Get incident details: To get the actual incident record (e.g., its number), you would need to perform a secondary GlideRecord query based on the aggregated data. -Dependencies -This script uses standard ServiceNow APIs (GlideAggregate, gs). No external libraries are required. -Support -For any questions or issues, please open an issue in this repository. From 4f13513309152f52f807ad918b18ad9cd089bf94 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 09:42:00 +0530 Subject: [PATCH 14/30] Delete Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js --- .../script.js | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js diff --git a/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js b/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js deleted file mode 100644 index 4aabd461b1..0000000000 --- a/Core ServiceNow APIs/GlideAggregate/Find oldest Incident based Assignment Groups/script.js +++ /dev/null @@ -1,11 +0,0 @@ -var ga = new GlideAggregate('incident'); - ga.addActiveQuery(); - ga.addAggregate('MIN', 'opened_at'); - ga.groupBy('assignment_group'); - ga.query(); - - while (ga.next()) { - var group = ga.assignment_group.getDisplayValue(); - var oldestIncidentDate = ga.getAggregate('MIN', 'opened_at'); - gs.info("Oldest open incident for " + group + " was created on: " + oldestIncidentDate); - } From 5328ddaee5e89cd18b04b5b2f8c22b561303b1c4 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 09:42:25 +0530 Subject: [PATCH 15/30] Delete Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md --- .../GlideJsonPath Reader Example/README.md | 43 ------------------- 1 file changed, 43 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md diff --git a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md deleted file mode 100644 index 8106a2c022..0000000000 --- a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/README.md +++ /dev/null @@ -1,43 +0,0 @@ -Parse JSON with GlideJsonPath -This script demonstrates how to use the GlideJsonPath API in ServiceNow to parse a complex JSON string and extract specific data. In this example, it efficiently retrieves an array of player names from a nested JSON structure representing cricket team information. - -Overview -The script performs the following actions: -Initializes GlideJsonPath: A new instance of GlideJsonPath is created, passing the target JSON string as a parameter. -Reads data with JSONPath: The read() method is called with a JSONPath expression to extract the desired data. The expression "$['lib']['jsonpath']['cricket']['players'][*]" navigates to the array of players: -$ represents the root of the JSON object. -['lib'], ['jsonpath'], and ['cricket'] use bracket notation to traverse the nested object structure. -['players'] accesses the array of player names. -[*] is a wildcard that returns all elements in the players array. -Stores the result: The extracted array of player names is stored in the l variable. -Use case -This script is useful in scenarios where you need to parse JSON data returned from a REST API call or an integration. By using GlideJsonPath, you can quickly and efficiently extract specific pieces of information without writing complex code to traverse the entire JSON object manually. -How to use -This script is intended for use in a server-side context within ServiceNow, such as a Script Include or Business Rule. -Example: Script Include -javascript -var CricketJsonParser = Class.create(); -CricketJsonParser.prototype = { - initialize: function() { - }, - - getPlayers: function() { - var jsonString = '{"lib":{"jsonpath":{"cricket":{"name":"India","players":["Rohit","Dhoni","Kholi"]}}}}'; - var v = new GlideJsonPath(jsonString); - var playerNames = v.read("$['lib']['jsonpath']['cricket']['players'][*]"); - - // At this point, `playerNames` is a JavaObject array. - // You can log it or process it further. - gs.info("Extracted players: " + JSON.stringify(playerNames)); - - return playerNames; - }, - - type: 'CricketJsonParser' -}; -Use code with caution. - -Customization -JSON Data: Replace the hardcoded jsonString with the variable that holds your JSON data (e.g., the body of a REST response). -JSONPath Expression: Modify the JSONPath expression in the read() method to extract different data from your JSON structure. -Result Handling: The read() function returns a JavaObject array, not a standard JavaScript array. If you need to use JavaScript array methods (like forEach), you may need to convert it. From 6750b61a4e6fdf4846b562fe97ace4a8607eb8e8 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Tue, 28 Oct 2025 09:43:01 +0530 Subject: [PATCH 16/30] Delete Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js --- .../GlideJsonPath/GlideJsonPath Reader Example/script.js | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js diff --git a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js b/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js deleted file mode 100644 index 063812aad7..0000000000 --- a/Core ServiceNow APIs/GlideJsonPath/GlideJsonPath Reader Example/script.js +++ /dev/null @@ -1,3 +0,0 @@ -var v = new GlideJsonPath('{"lib":{"jsonpath":{"cricket":{"name":"India","players":["Rohit","Dhoni","Kholi"]}}}}'); -var l = v.read("$['lib']['jsonpath']['cricket']['players'][*]"); - From 8f9be4ec775fd39dea916f96087e7ce256dc792b Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:06:13 +0530 Subject: [PATCH 17/30] Create script.js This Business Rule runs 'before' a record is deleted from the 'sn_compliance_policy' table. Its purpose is to prevent a policy from being deleted if it is currently linked to any active controls. This helps maintain data integrity and prevents the creation of orphaned or invalidated compliance records. --- .../script.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js diff --git a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js new file mode 100644 index 0000000000..4ac6383481 --- /dev/null +++ b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js @@ -0,0 +1,50 @@ +(function executeRule(current, previous /*null when async*/ ) { + // This Business Rule runs 'before' a record is deleted from the 'sn_compliance_policy' table. + // Its purpose is to prevent a policy from being deleted if it is currently linked to any active controls. + // This helps maintain data integrity and prevents the creation of orphaned or invalidated compliance records. + + + // 'sn_compliance_m2m_policy_policy_statement'. This table links policies (via the 'document' field) + // to control statements (via the 'content' field). Using GlideAggregate is more + // performant than GlideRecord for counting records, as it performs the aggregation + // directly in the database. + var grControlAggregate = new GlideAggregate('sn_compliance_m2m_policy_policy_statement'); + + // Add a query to filter for records in the m2m table where the 'document' field matches + // the sys_id of the policy record currently being deleted. + grControlAggregate.addQuery('document', current.getUniqueValue()); + + // Add a second query using 'dot-walking' to filter for records where the related + // control statement ('content' field) is currently active. This ensures only active + // controls are considered. + grControlAggregate.addQuery('content.active', true); + + // Set the aggregate function to COUNT. This tells the database to return the total + // number of records that match the query conditions. + grControlAggregate.addAggregate('COUNT'); + + // Execute the database query. + grControlAggregate.query(); + + // Initialize a variable to store the count of active controls. + var activeControlCount = 0; + + // Check if the query returned any results. If it did, retrieve the count. + // Note: GlideAggregate.next() returns a row even if the count is zero. + if (grControlAggregate.next()) { + // Retrieve the aggregated count result and assign it to the variable. + activeControlCount = grControlAggregate.getAggregate('COUNT'); + } + + // Check if the count of active controls is greater than zero. + if (activeControlCount > 0) { + // If active controls were found, add an error message to display to the user. + // The message includes the count for better clarity. + gs.addErrorMessage('Cannot delete this policy because it has ' + activeControlCount + ' active controls linked to it.'); + + // This crucial line aborts the current database transaction (the delete operation). + // It prevents the policy record from being deleted. + current.setAbortAction(true); + } + +})(current, previous); From 8083ec99e7636b82402921dc46cb26458086955b Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:07:49 +0530 Subject: [PATCH 18/30] Create README.md --- .../README.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md diff --git a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md new file mode 100644 index 0000000000..6a0e6ca820 --- /dev/null +++ b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md @@ -0,0 +1,36 @@ +GRC Policy Deletion Guard +Overview +This Business Rule enhances data integrity for ServiceNow's Governance, Risk, and Compliance (GRC) module. It prevents the deletion of a sn_compliance_policy record if it is still associated with any active controls. This ensures that policy changes are managed properly and prevents compliance gaps that could occur if a foundational policy is removed while dependent controls are still active. +Details +Script Name: Prevent Delete of Policy with Active Controls +Target Table: sn_compliance_policy +Run Time: before delete +Action: Prevents a policy from being deleted if it has active, linked controls. Displays an error message to the user and aborts the deletion action. +Logic: +Uses GlideAggregate for an efficient query on the many-to-many (m2m) table (sn_compliance_m2m_policy_policy_statement) that links policies to control statements. +The query filters for records where: +The document field matches the sys_id of the policy being deleted. +The related content record (the control statement) has its active field set to true. +A COUNT aggregate is performed on the filtered records. +If the count is greater than zero, the script adds an error message to the form and aborts the deletion process using current.setAbortAction(true). +Business Rule Configuration +To implement this functionality, configure the following settings in the Business Rule record: +Name: Prevent Delete of Policy with Active Controls +Table: sn_compliance_policy +When to run: +When: before +Delete: checked +Advanced: checked + + +Purpose and Benefits +This Business Rule provides the following benefits to the GRC application: +Data Integrity: Prevents the accidental or erroneous deletion of policies that are still in active use, thereby preventing broken relationships in your GRC data model. +Controlled Changes: Enforces a process where administrators must first inactivate or re-associate all controls linked to a policy before it can be deleted, ensuring proper change management. +User Feedback: Provides clear and immediate feedback to the user, explaining why the requested action was denied and outlining the necessary steps to proceed. +Performance: Utilizes the efficient GlideAggregate method, which scales well even on large production instances. +Usage +This script is a core part of GRC data governance. If a user attempts to delete a policy with active controls, they will see an error message and the deletion will be stopped. The user must navigate to the related controls and either make them inactive or associate them with a different policy before attempting to delete the original policy again. + + + From 8e92c2f95653bb396bb0d963e2c49c6dc234d031 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:09:28 +0530 Subject: [PATCH 19/30] Delete Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md --- .../README.md | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md diff --git a/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md b/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md deleted file mode 100644 index 6f7afc5eac..0000000000 --- a/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md +++ /dev/null @@ -1,37 +0,0 @@ -Key features -Automatic problem creation: The script uses a GlideAggregate query to count the number of incidents opened for a specific CI. -Time-based threshold: Problems are only created if more than five incidents are opened within a 60-minute window. -Targeted incidents: The script focuses on incidents related to the same CI, making it effective for identifying recurring infrastructure issues. -Customizable conditions: The number of incidents and the time frame are easily configurable within the script. -Efficient performance: The use of GlideAggregate ensures the database is queried efficiently, minimizing performance impact. - -How it works -The script is designed to be executed as a server-side script, typically within a Business Rule. When an incident is inserted or updated, the script performs the following actions: -Queries incidents: It executes a GlideAggregate query on the incident table. -Sets conditions: The query is filtered to count all incidents that meet the following conditions: -Same CI: The incident's cmdb_ci matches the cmdb_ci of the current record. -Within the last hour: The opened_at time is within the last 60 minutes. -Evaluates count: After the query is run, the script checks if the count of matching incidents exceeds the threshold (in this case, 5). -Creates problem: If the threshold is exceeded, a new problem record is initialized. -The short_description is automatically populated with a descriptive message. -The cmdb_ci is linked to the new problem record. -The new record is then inserted into the database. -Implementation steps -Create a Business Rule: -Navigate to System Definition > Business Rules. -Click New. -Configure the Business Rule: -Name: Auto Create Problem from Multiple Incidents -Table: Incident [incident] -Advanced: true -When to run: Select after and check the Insert and Update checkboxes. This ensures the script runs after an incident has been saved. -Filter conditions: Optionally, you can add conditions to limit when the script runs (e.g., cmdb_ci is not empty). -Add the script: -Navigate to the Advanced tab. -Copy and paste the script into the Script field. -Customize (optional): -Number of incidents: Change the > 5 value to adjust the threshold. -Time frame: Adjust the gs.minutesAgoStart(60) value to change the time window. -Other conditions: If you need to check for specific incident statuses or categories, add more addQuery lines to the GlideAggregate call. -Save the Business Rule. -Customization examples From c1a0b3c17685cbf3a5039aa00b9245c7f5bd9d6b Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:09:55 +0530 Subject: [PATCH 20/30] Delete Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js --- .../script.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js diff --git a/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js b/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js deleted file mode 100644 index dcec063918..0000000000 --- a/Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js +++ /dev/null @@ -1,13 +0,0 @@ -var incidentCheck = new GlideAggregate('incident'); - incidentCheck.addQuery('cmdb_ci', current.cmdb_ci); // Or any specific CI - incidentCheck.addQuery('opened_at', '>', 'javascript:gs.minutesAgoStart(60)'); - incidentCheck.addAggregate('COUNT'); - incidentCheck.query(); - - if (incidentCheck.next() && parseInt(incidentCheck.getAggregate('COUNT')) > 5) { - var problem = new GlideRecord('problem'); - problem.initialize(); - problem.short_description = 'Multiple incidents reported for CI: ' + current.cmdb_ci.getDisplayValue(); - problem.cmdb_ci = current.cmdb_ci; - problem.insert(); - } From ed09dacb7aecb46dde24ec4dd847101c4266d99b Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:19:43 +0530 Subject: [PATCH 21/30] Update script.js --- .../script.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js index 4ac6383481..e2697fc20b 100644 --- a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js +++ b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js @@ -1,9 +1,11 @@ (function executeRule(current, previous /*null when async*/ ) { - // This Business Rule runs 'before' a record is deleted from the 'sn_compliance_policy' table. - // Its purpose is to prevent a policy from being deleted if it is currently linked to any active controls. - // This helps maintain data integrity and prevents the creation of orphaned or invalidated compliance records. + // This Business Rule runs 'before' a record is updated on the 'sn_compliance_policy' table. + // Its purpose is to prevent a policy from being retired if it is currently linked to any active Control Objectives. + // This enforces a proper decommissioning process, ensuring that Control Objectives are delinked. + // before the policy that governs them, thereby preventing compliance gaps. + // The condition for this rule would be: 'State' changes to 'Retired'. - + // Instantiate a GlideAggregate object on the many-to-many (m2m) table // 'sn_compliance_m2m_policy_policy_statement'. This table links policies (via the 'document' field) // to control statements (via the 'content' field). Using GlideAggregate is more // performant than GlideRecord for counting records, as it performs the aggregation @@ -11,7 +13,7 @@ var grControlAggregate = new GlideAggregate('sn_compliance_m2m_policy_policy_statement'); // Add a query to filter for records in the m2m table where the 'document' field matches - // the sys_id of the policy record currently being deleted. + // the sys_id of the policy record currently being retired. grControlAggregate.addQuery('document', current.getUniqueValue()); // Add a second query using 'dot-walking' to filter for records where the related @@ -38,12 +40,12 @@ // Check if the count of active controls is greater than zero. if (activeControlCount > 0) { - // If active controls were found, add an error message to display to the user. + // If active control objectives were found, add an error message to display to the user. // The message includes the count for better clarity. - gs.addErrorMessage('Cannot delete this policy because it has ' + activeControlCount + ' active controls linked to it.'); + gs.addErrorMessage('Cannot retire this policy because it has ' + activeControlCount + ' active control objectives linked to it. All control objectives must be delinked first.'); - // This crucial line aborts the current database transaction (the delete operation). - // It prevents the policy record from being deleted. + // This crucial line aborts the current database transaction (the update operation). + // It prevents the policy record from being marked as 'Retired'. current.setAbortAction(true); } From 23bf21c7cf17b74ea0b2bff798e292c41f32d5f7 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:21:48 +0530 Subject: [PATCH 22/30] Create Prevent Retirement of Policy with Active Control objectives --- .../Prevent Retirement of Policy with Active Control objectives | 1 + 1 file changed, 1 insertion(+) create mode 100644 Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives diff --git a/Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives b/Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives @@ -0,0 +1 @@ + From 901ea70e2886218421dd6d53e8b1abf45d588969 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:22:18 +0530 Subject: [PATCH 23/30] Delete Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives --- .../Prevent Retirement of Policy with Active Control objectives | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives diff --git a/Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives b/Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives deleted file mode 100644 index 8b13789179..0000000000 --- a/Server-Side Components/Business Rules/Prevent Retirement of Policy with Active Control objectives +++ /dev/null @@ -1 +0,0 @@ - From 970549fa81a45c773cbbab1b211a36215e3e1b65 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:24:16 +0530 Subject: [PATCH 24/30] Create script.js This Business Rule runs 'before' a record is updated on the 'sn_compliance_policy' table. Its purpose is to prevent a policy from being retired if it is currently linked to any active controls. This enforces a proper decommissioning process, ensuring that controls are retired before the policy that governs them, thereby preventing compliance gaps. The condition for this rule would be: 'State' changes to 'Retired'. --- .../GRC Policy Retirement Gaurd/script.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js diff --git a/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js new file mode 100644 index 0000000000..4412d5a82b --- /dev/null +++ b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js @@ -0,0 +1,52 @@ +(function executeRule(current, previous /*null when async*/ ) { + // This Business Rule runs 'before' a record is updated on the 'sn_compliance_policy' table. + // Its purpose is to prevent a policy from being retired if it is currently linked to any active controls. + // This enforces a proper decommissioning process, ensuring that controls are retired + // before the policy that governs them, thereby preventing compliance gaps. + // The condition for this rule would be: 'State' changes to 'Retired'. + + // Instantiate a GlideAggregate object on the many-to-many (m2m) table + // 'sn_compliance_m2m_policy_policy_statement'. This table links policies (via the 'document' field) + // to control statements (via the 'content' field). Using GlideAggregate is more + // performant than GlideRecord for counting records, as it performs the aggregation + // directly in the database. + var grControlAggregate = new GlideAggregate('sn_compliance_m2m_policy_policy_statement'); + + // Add a query to filter for records in the m2m table where the 'document' field matches + // the sys_id of the policy record currently being retired. + grControlAggregate.addQuery('document', current.getUniqueValue()); + + // Add a second query using 'dot-walking' to filter for records where the related + // control statement ('content' field) is currently active. This ensures only active + // controls are considered. + grControlAggregate.addQuery('content.active', true); + + // Set the aggregate function to COUNT. This tells the database to return the total + // number of records that match the query conditions. + grControlAggregate.addAggregate('COUNT'); + + // Execute the database query. + grControlAggregate.query(); + + // Initialize a variable to store the count of active controls. + var activeControlCount = 0; + + // Check if the query returned any results. If it did, retrieve the count. + // Note: GlideAggregate.next() returns a row even if the count is zero. + if (grControlAggregate.next()) { + // Retrieve the aggregated count result and assign it to the variable. + activeControlCount = grControlAggregate.getAggregate('COUNT'); + } + + // Check if the count of active controls is greater than zero. + if (activeControlCount > 0) { + // If active controls were found, add an error message to display to the user. + // The message includes the count for better clarity. + gs.addErrorMessage('Cannot retire this policy because it has ' + activeControlCount + ' active controls linked to it. All controls must be retired first.'); + + // This crucial line aborts the current database transaction (the update operation). + // It prevents the policy record from being marked as 'Retired'. + current.setAbortAction(true); + } + +})(current, previous); From a357cb823491db120e8a57e466d05052200a8d2f Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:25:58 +0530 Subject: [PATCH 25/30] Update script.js --- .../GRC Policy Retirement Gaurd/script.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js index 4412d5a82b..259366aef8 100644 --- a/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js +++ b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js @@ -1,7 +1,7 @@ (function executeRule(current, previous /*null when async*/ ) { // This Business Rule runs 'before' a record is updated on the 'sn_compliance_policy' table. - // Its purpose is to prevent a policy from being retired if it is currently linked to any active controls. - // This enforces a proper decommissioning process, ensuring that controls are retired + // Its purpose is to prevent a policy from being retired if it is currently linked to any active Control Objective. + // This enforces a proper decommissioning process, ensuring that Control Objective are delinked. // before the policy that governs them, thereby preventing compliance gaps. // The condition for this rule would be: 'State' changes to 'Retired'. @@ -18,7 +18,7 @@ // Add a second query using 'dot-walking' to filter for records where the related // control statement ('content' field) is currently active. This ensures only active - // controls are considered. + // Control Objective are considered. grControlAggregate.addQuery('content.active', true); // Set the aggregate function to COUNT. This tells the database to return the total @@ -28,7 +28,7 @@ // Execute the database query. grControlAggregate.query(); - // Initialize a variable to store the count of active controls. + // Initialize a variable to store the count of active Control Objective. var activeControlCount = 0; // Check if the query returned any results. If it did, retrieve the count. @@ -38,11 +38,11 @@ activeControlCount = grControlAggregate.getAggregate('COUNT'); } - // Check if the count of active controls is greater than zero. + // Check if the count of active Control Objective is greater than zero. if (activeControlCount > 0) { - // If active controls were found, add an error message to display to the user. + // If active Control Objective were found, add an error message to display to the user. // The message includes the count for better clarity. - gs.addErrorMessage('Cannot retire this policy because it has ' + activeControlCount + ' active controls linked to it. All controls must be retired first.'); + gs.addErrorMessage('Cannot retire this policy because it has ' + activeControlCount + ' active Control Objective linked to it. All Control Objective must be delinked first.'); // This crucial line aborts the current database transaction (the update operation). // It prevents the policy record from being marked as 'Retired'. From 28be06b112ca73cc960583015864aed0a1ac3fc3 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:29:03 +0530 Subject: [PATCH 26/30] Create README.md --- .../GRC Policy Retirement Gaurd/README.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md diff --git a/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md new file mode 100644 index 0000000000..f840a0731a --- /dev/null +++ b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md @@ -0,0 +1,37 @@ +GRC Policy Retirement Guard with Control Objective Check +Overview +This Business Rule enhances data integrity and process governance within ServiceNow's GRC module. It prevents a sn_compliance_policy record from being marked with the "Retired" state if it is still associated with any active Control Objectives. The rule enforces a proper decommissioning process, ensuring that all dependent Control Objectives are either made inactive or delinked before the policy itself can be retired. +Details +Script Name: Prevent Retire of Policy with Active Control Objectives +Target Table: sn_compliance_policy +Run Time: before update +Condition: State changes to Retired +Action: Prevents a policy from being retired if it has active, linked Control Objectives. It displays an error message to the user and aborts the update action. +Logic: +Efficient Counting: Uses GlideAggregate for a highly performant query on the many-to-many (m2m) table (sn_compliance_m2m_policy_policy_statement), which links policies to control statements (in this case, acting as Control Objectives). +Query Filtering: The query targets the m2m table and filters records where: +The document field matches the sys_id of the policy being updated. +The related content record (the Control Objective) has its active field set to true. +Aborts Action: If the count of active Control Objectives is greater than zero, the script: +Displays an informative error message to the user. +Aborts the update process using current.setAbortAction(true), preventing the policy from being set to Retired. +Business Rule Configuration +To implement this functionality, configure the following settings in the Business Rule record: +Name: Prevent Retire of Policy with Active Control Objectives +Table: sn_compliance_policy +When to run: +When: before +Update: checked +Condition: [State] [changes to] [Retired] +Advanced: checked + + +Purpose and Benefits +This Business Rule provides the following benefits to the GRC application: +Process Governance: Enforces a controlled process for policy retirement, ensuring that all dependent Control Objectives are handled appropriately before the policy is decommissioned. +Data Integrity: Prevents the creation of orphaned Control Objectives or inconsistencies in compliance reporting. +Compliance: Ensures that compliance teams maintain an accurate and up-to-date record of active policies and their underlying Control Objectives. +User Feedback: Provides immediate and clear feedback to the user, explaining why the retirement action was denied and outlining the necessary steps to proceed. +Performance: Utilizes the efficient GlideAggregate method, which is best practice for performing counts on large tables. +Usage +This script is a core part of GRC data governance. If a user attempts to set a policy's State to Retired while active Control Objectives are still linked, they will see an error message and the update will be stopped. The user must first either inactivate or delink all related Control Objectives before attempting to retire the policy again. From 0a82c5804821114c09e109a5e605567d6585ddb7 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:32:26 +0530 Subject: [PATCH 27/30] Delete Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md --- .../GRC Policy Retirement Gaurd/README.md | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md diff --git a/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md deleted file mode 100644 index f840a0731a..0000000000 --- a/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md +++ /dev/null @@ -1,37 +0,0 @@ -GRC Policy Retirement Guard with Control Objective Check -Overview -This Business Rule enhances data integrity and process governance within ServiceNow's GRC module. It prevents a sn_compliance_policy record from being marked with the "Retired" state if it is still associated with any active Control Objectives. The rule enforces a proper decommissioning process, ensuring that all dependent Control Objectives are either made inactive or delinked before the policy itself can be retired. -Details -Script Name: Prevent Retire of Policy with Active Control Objectives -Target Table: sn_compliance_policy -Run Time: before update -Condition: State changes to Retired -Action: Prevents a policy from being retired if it has active, linked Control Objectives. It displays an error message to the user and aborts the update action. -Logic: -Efficient Counting: Uses GlideAggregate for a highly performant query on the many-to-many (m2m) table (sn_compliance_m2m_policy_policy_statement), which links policies to control statements (in this case, acting as Control Objectives). -Query Filtering: The query targets the m2m table and filters records where: -The document field matches the sys_id of the policy being updated. -The related content record (the Control Objective) has its active field set to true. -Aborts Action: If the count of active Control Objectives is greater than zero, the script: -Displays an informative error message to the user. -Aborts the update process using current.setAbortAction(true), preventing the policy from being set to Retired. -Business Rule Configuration -To implement this functionality, configure the following settings in the Business Rule record: -Name: Prevent Retire of Policy with Active Control Objectives -Table: sn_compliance_policy -When to run: -When: before -Update: checked -Condition: [State] [changes to] [Retired] -Advanced: checked - - -Purpose and Benefits -This Business Rule provides the following benefits to the GRC application: -Process Governance: Enforces a controlled process for policy retirement, ensuring that all dependent Control Objectives are handled appropriately before the policy is decommissioned. -Data Integrity: Prevents the creation of orphaned Control Objectives or inconsistencies in compliance reporting. -Compliance: Ensures that compliance teams maintain an accurate and up-to-date record of active policies and their underlying Control Objectives. -User Feedback: Provides immediate and clear feedback to the user, explaining why the retirement action was denied and outlining the necessary steps to proceed. -Performance: Utilizes the efficient GlideAggregate method, which is best practice for performing counts on large tables. -Usage -This script is a core part of GRC data governance. If a user attempts to set a policy's State to Retired while active Control Objectives are still linked, they will see an error message and the update will be stopped. The user must first either inactivate or delink all related Control Objectives before attempting to retire the policy again. From 16b223e3c22bcf6716a41590efeef624a6d8a30b Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:34:26 +0530 Subject: [PATCH 28/30] Create README.md --- .../GRC Policy Retirement Gaurd/README.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md diff --git a/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md new file mode 100644 index 0000000000..f840a0731a --- /dev/null +++ b/Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md @@ -0,0 +1,37 @@ +GRC Policy Retirement Guard with Control Objective Check +Overview +This Business Rule enhances data integrity and process governance within ServiceNow's GRC module. It prevents a sn_compliance_policy record from being marked with the "Retired" state if it is still associated with any active Control Objectives. The rule enforces a proper decommissioning process, ensuring that all dependent Control Objectives are either made inactive or delinked before the policy itself can be retired. +Details +Script Name: Prevent Retire of Policy with Active Control Objectives +Target Table: sn_compliance_policy +Run Time: before update +Condition: State changes to Retired +Action: Prevents a policy from being retired if it has active, linked Control Objectives. It displays an error message to the user and aborts the update action. +Logic: +Efficient Counting: Uses GlideAggregate for a highly performant query on the many-to-many (m2m) table (sn_compliance_m2m_policy_policy_statement), which links policies to control statements (in this case, acting as Control Objectives). +Query Filtering: The query targets the m2m table and filters records where: +The document field matches the sys_id of the policy being updated. +The related content record (the Control Objective) has its active field set to true. +Aborts Action: If the count of active Control Objectives is greater than zero, the script: +Displays an informative error message to the user. +Aborts the update process using current.setAbortAction(true), preventing the policy from being set to Retired. +Business Rule Configuration +To implement this functionality, configure the following settings in the Business Rule record: +Name: Prevent Retire of Policy with Active Control Objectives +Table: sn_compliance_policy +When to run: +When: before +Update: checked +Condition: [State] [changes to] [Retired] +Advanced: checked + + +Purpose and Benefits +This Business Rule provides the following benefits to the GRC application: +Process Governance: Enforces a controlled process for policy retirement, ensuring that all dependent Control Objectives are handled appropriately before the policy is decommissioned. +Data Integrity: Prevents the creation of orphaned Control Objectives or inconsistencies in compliance reporting. +Compliance: Ensures that compliance teams maintain an accurate and up-to-date record of active policies and their underlying Control Objectives. +User Feedback: Provides immediate and clear feedback to the user, explaining why the retirement action was denied and outlining the necessary steps to proceed. +Performance: Utilizes the efficient GlideAggregate method, which is best practice for performing counts on large tables. +Usage +This script is a core part of GRC data governance. If a user attempts to set a policy's State to Retired while active Control Objectives are still linked, they will see an error message and the update will be stopped. The user must first either inactivate or delink all related Control Objectives before attempting to retire the policy again. From c1f3d5b235feda74f4f1eb92e6467c9b21fb6eab Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:35:31 +0530 Subject: [PATCH 29/30] Delete Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md --- .../README.md | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md diff --git a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md deleted file mode 100644 index 6a0e6ca820..0000000000 --- a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/README.md +++ /dev/null @@ -1,36 +0,0 @@ -GRC Policy Deletion Guard -Overview -This Business Rule enhances data integrity for ServiceNow's Governance, Risk, and Compliance (GRC) module. It prevents the deletion of a sn_compliance_policy record if it is still associated with any active controls. This ensures that policy changes are managed properly and prevents compliance gaps that could occur if a foundational policy is removed while dependent controls are still active. -Details -Script Name: Prevent Delete of Policy with Active Controls -Target Table: sn_compliance_policy -Run Time: before delete -Action: Prevents a policy from being deleted if it has active, linked controls. Displays an error message to the user and aborts the deletion action. -Logic: -Uses GlideAggregate for an efficient query on the many-to-many (m2m) table (sn_compliance_m2m_policy_policy_statement) that links policies to control statements. -The query filters for records where: -The document field matches the sys_id of the policy being deleted. -The related content record (the control statement) has its active field set to true. -A COUNT aggregate is performed on the filtered records. -If the count is greater than zero, the script adds an error message to the form and aborts the deletion process using current.setAbortAction(true). -Business Rule Configuration -To implement this functionality, configure the following settings in the Business Rule record: -Name: Prevent Delete of Policy with Active Controls -Table: sn_compliance_policy -When to run: -When: before -Delete: checked -Advanced: checked - - -Purpose and Benefits -This Business Rule provides the following benefits to the GRC application: -Data Integrity: Prevents the accidental or erroneous deletion of policies that are still in active use, thereby preventing broken relationships in your GRC data model. -Controlled Changes: Enforces a process where administrators must first inactivate or re-associate all controls linked to a policy before it can be deleted, ensuring proper change management. -User Feedback: Provides clear and immediate feedback to the user, explaining why the requested action was denied and outlining the necessary steps to proceed. -Performance: Utilizes the efficient GlideAggregate method, which scales well even on large production instances. -Usage -This script is a core part of GRC data governance. If a user attempts to delete a policy with active controls, they will see an error message and the deletion will be stopped. The user must navigate to the related controls and either make them inactive or associate them with a different policy before attempting to delete the original policy again. - - - From 98b8437fd9e028c21fa94c8027b48e3a39ae4220 Mon Sep 17 00:00:00 2001 From: trimbakeshmadhan-109 Date: Wed, 29 Oct 2025 02:35:43 +0530 Subject: [PATCH 30/30] Delete Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js --- .../script.js | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js diff --git a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js b/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js deleted file mode 100644 index e2697fc20b..0000000000 --- a/Server-Side Components/Business Rules/Prevent Deletion of Policy with Active Controls/script.js +++ /dev/null @@ -1,52 +0,0 @@ -(function executeRule(current, previous /*null when async*/ ) { - // This Business Rule runs 'before' a record is updated on the 'sn_compliance_policy' table. - // Its purpose is to prevent a policy from being retired if it is currently linked to any active Control Objectives. - // This enforces a proper decommissioning process, ensuring that Control Objectives are delinked. - // before the policy that governs them, thereby preventing compliance gaps. - // The condition for this rule would be: 'State' changes to 'Retired'. - - // Instantiate a GlideAggregate object on the many-to-many (m2m) table - // 'sn_compliance_m2m_policy_policy_statement'. This table links policies (via the 'document' field) - // to control statements (via the 'content' field). Using GlideAggregate is more - // performant than GlideRecord for counting records, as it performs the aggregation - // directly in the database. - var grControlAggregate = new GlideAggregate('sn_compliance_m2m_policy_policy_statement'); - - // Add a query to filter for records in the m2m table where the 'document' field matches - // the sys_id of the policy record currently being retired. - grControlAggregate.addQuery('document', current.getUniqueValue()); - - // Add a second query using 'dot-walking' to filter for records where the related - // control statement ('content' field) is currently active. This ensures only active - // controls are considered. - grControlAggregate.addQuery('content.active', true); - - // Set the aggregate function to COUNT. This tells the database to return the total - // number of records that match the query conditions. - grControlAggregate.addAggregate('COUNT'); - - // Execute the database query. - grControlAggregate.query(); - - // Initialize a variable to store the count of active controls. - var activeControlCount = 0; - - // Check if the query returned any results. If it did, retrieve the count. - // Note: GlideAggregate.next() returns a row even if the count is zero. - if (grControlAggregate.next()) { - // Retrieve the aggregated count result and assign it to the variable. - activeControlCount = grControlAggregate.getAggregate('COUNT'); - } - - // Check if the count of active controls is greater than zero. - if (activeControlCount > 0) { - // If active control objectives were found, add an error message to display to the user. - // The message includes the count for better clarity. - gs.addErrorMessage('Cannot retire this policy because it has ' + activeControlCount + ' active control objectives linked to it. All control objectives must be delinked first.'); - - // This crucial line aborts the current database transaction (the update operation). - // It prevents the policy record from being marked as 'Retired'. - current.setAbortAction(true); - } - -})(current, previous);