Help with Script for Automatically Updating CI Install Status Based on Discovery Date
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited 3 weeks ago
Hello Everyone,
I’m working on a story to automatically update the install_status of Configuration Items (CIs) based on the number of days since their last discovery. Specifically, I need to update CIs with the following criteria:
- If a CI hasn’t been discovered in the last 45–90 days, its
install_statusshould be changed to Absent (100). - If a CI hasn’t been discovered in the last 90+ days, its
install_statusshould be changed to Retired (7 ), and theoperational_statusshould be set to Non-Operational (2).
Class names: cmdb_ci_db_mssql_database,cmdb_ci_vm_instance,cmdb_ci_db_ora_instance,cmdb_ci_linux_server,cmdb_ci_vmware_instance
I created a system property with all the classes mentioned above. Property name(ci.auto.manage.classes)
Script:
(function () {
gs.info('=== CI Auto Absent / Retire Job STARTED ===');
// System Properties
var classList = gs.getProperty('ci.auto.manage.classes', '').split(',');
var absentDays = parseInt(gs.getProperty('ci.absent.days', '45'), 10);
var retireDays = parseInt(gs.getProperty('ci.retire.days', '90'), 10);
var absentInstallStatus = gs.getProperty('ci.absent.install_status', '100');
var retireInstallStatus = gs.getProperty('ci.retire.install_status', '7');
var retireOperStatus = gs.getProperty('ci.retire.operational_status', '2');
// Date calculations
var now = new GlideDateTime();
var absentCutoff = new GlideDateTime(now);
absentCutoff.addDaysUTC(-absentDays);
var retireCutoff = new GlideDateTime(now);
retireCutoff.addDaysUTC(-retireDays);
gs.info('Absent Cutoff : ' + absentCutoff.getDisplayValue());
gs.info('Retire Cutoff : ' + retireCutoff.getDisplayValue());
for (var i = 0; i < classList.length; i++) {
var table = classList[i].trim();
if (!table) continue;
gs.info('--- Processing Table: ' + table + ' ---');
var gr = new GlideRecord(table);
gr.addNotNullQuery('last_discovered');
gr.query();
while (gr.next()) {
var lastDiscovered = gr.last_discovered;
/* ===============================
RETIRE (>= 90 days)
================================ */
if (lastDiscovered < retireCutoff) {
if (gr.install_status != retireInstallStatus) {
gr.setWorkflow(false);
gr.autoSysFields(false);
gr.install_status = retireInstallStatus;
gr.operational_status = retireOperStatus;
gr.update();
gs.info('✅ RETIRED CI: ' + gr.name + ' | ' + gr.sys_id);
}
}
/* ===============================
ABSENT (45–89 days)
install_status ONLY
================================ */
else if (lastDiscovered < absentCutoff) {
if (gr.install_status != absentInstallStatus) {
gr.setWorkflow(false);
gr.autoSysFields(false);
gr.install_status = absentInstallStatus;
// ✅ operational_status untouched
gr.update();
gs.info('MARKED ABSENT (install_status only): ' + gr.name + ' | ' + gr.sys_id);
}
}
}
}
gs.info('=== CI Auto Absent / Retire Job COMPLETED ===');
})();
I created the system properties mentioned in the script.
Could you please help me review this script, and let me know if there are any improvements or changes I should make to ensure it works as expected? Specifically, I want to confirm if the logic for updating the install_status and operational_status is correct, and if any optimizations can be made to improve performance.
@Ankur Bawiskar @Amit Gujarathi @Ravi Gaurav
Thanks,
Abdul
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Good script overall and the logic is solid. Here are my review comments and suggested improvements:
What is working correctly:
- System property driven configuration is a good practice for maintainability
- Using setWorkflow(false) and autoSysFields(false) is appropriate for bulk updates
- The condition check before updating (gr.install_status != retireInstallStatus) avoids unnecessary updates, which is good for performance
- The retire vs absent date logic is correctly ordered, retire check comes first
Issues and improvements:
1. Date comparison may not work as expected
Comparing GlideDateTime objects directly using < is not reliable in ServiceNow. Use the before() method instead:
// Instead of this
if (lastDiscovered < retireCutoff)
// Use this
var lastDiscoveredGDT = new GlideDateTime(gr.getValue('last_discovered'));
if (lastDiscoveredGDT.before(retireCutoff))Make sure you are also using gr.getValue('last_discovered') to get the raw value rather than gr.last_discovered directly.
2. Performance concern with large CMDB tables
CMDB tables like cmdb_ci_linux_server and cmdb_ci_vm_instance can have hundreds of thousands of records. Querying all records with just an addNotNullQuery on last_discovered will be very heavy. Add a date filter to limit the query to only records that need processing:
var gr = new GlideRecord(table);
gr.addNotNullQuery('last_discovered');
gr.addQuery('last_discovered', '<', absentCutoff.getValue());
gr.query();This way you are only fetching CIs that are actually candidates for update rather than every CI in the table.
3. Add install_status filter to query
Since you are already checking gr.install_status before updating, push that check into the query as well to reduce the number of records fetched:
var gr = new GlideRecord(table);
gr.addNotNullQuery('last_discovered');
gr.addQuery('last_discovered', '<', absentCutoff.getValue());
gr.addQuery('install_status', '!=', retireInstallStatus);
gr.query();4. Consider running this as a Scheduled Job
If this is meant to run automatically on a regular basis, wrap it in a Scheduled Script Execution (System Scheduler) rather than a Fix Script so it runs on a defined schedule like nightly or weekly.
5. Minor: parseInt on status values is unnecessary
install_status and operational_status are integer fields but you are reading them from system properties as strings. ServiceNow will handle the type coercion automatically when you assign them to GlideRecord fields, so parseInt is not needed there. However it does not cause harm either.
Overall this is a well structured script. The main things to address before running in production are the date comparison method and the query optimization to avoid full table scans on large CMDB tables.
Hope this helps!
ServiceNow Developer & Admin
Builder of NowFixer | Free AI debugging tool for ServiceNow scripts
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
Hi @Abdul333,
Please review and try the below code and let me know if it worked:
(function () {
gs.info('=== CI Auto Absent / Retire Job STARTED ===');
// Properties
var classes = gs.getProperty('ci.auto.manage.classes', '').split(',');
var absentDays = parseInt(gs.getProperty('ci.absent.days', 45), 10);
var retireDays = parseInt(gs.getProperty('ci.retire.days', 90), 10);
var ABSENT = parseInt(gs.getProperty('ci.absent.install_status', 100), 10);
var RETIRED = parseInt(gs.getProperty('ci.retire.install_status', 7), 10);
var NON_OP = parseInt(gs.getProperty('ci.retire.operational_status', 2), 10);
// Cutoffs
var now = new GlideDateTime();
var absentCutoff = new GlideDateTime(now);
absentCutoff.addDaysUTC(-absentDays);
var retireCutoff = new GlideDateTime(now);
retireCutoff.addDaysUTC(-retireDays);
for (var i = 0; i < classes.length; i++) {
var table = classes[i].trim();
if (!table) continue;
/* ==========================
RETIRE (>= 90 days)
========================== */
var retireGR = new GlideRecord(table);
retireGR.addQuery('last_discovered', '<=', retireCutoff);
retireGR.addQuery('install_status', '!=', RETIRED);
retireGR.query();
while (retireGR.next()) {
retireGR.setWorkflow(false);
retireGR.autoSysFields(false);
retireGR.setValue('install_status', RETIRED);
retireGR.setValue('operational_status', NON_OP);
retireGR.update();
}
/* ==========================
ABSENT (45–89 days)
========================== */
var absentGR = new GlideRecord(table);
absentGR.addQuery('last_discovered', '<=', absentCutoff);
absentGR.addQuery('last_discovered', '>', retireCutoff);
absentGR.addQuery('install_status', '!=', ABSENT);
absentGR.query();
while (absentGR.next()) {
absentGR.setWorkflow(false);
absentGR.autoSysFields(false);
absentGR.setValue('install_status', ABSENT);
absentGR.update();
}
}
gs.info('=== CI Auto Absent / Retire Job COMPLETED ===');
})();
