Find your people. Pick a challenge. Ship something real. The CreatorCon Hackathon is coming to the Community Pavilion for one epic night. Every skill level, every role welcome. Join us on May 5th and learn more here.

Help with Script for Automatically Updating CI Install Status Based on Discovery Date

Abdul333
Tera Contributor

 

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_status should be changed to Absent (100).
  • If a CI hasn’t been discovered in the last 90+ days, its install_status should be changed to Retired (7 ), and the operational_status should 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

 

6 REPLIES 6

AhsanM
Tera Expert

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!

Ahsan
ServiceNow Developer & Admin
Builder of NowFixer | Free AI debugging tool for ServiceNow scripts

Kamva
Giga Guru

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 ===');

})();