SN Tricks

Production-Ready ServiceNow Scripts & Automation

Client Scripts (1)
Dynamic Field Population on Load
Automatically populate fields based on user information when form loads
Type: onLoad Table: incident ID: cs001
function onLoad() {
    // Get current user's department and location
    var userDept = g_user.department;
    var userLocation = g_user.location;
    
    // Set caller field to current user if empty
    if (g_form.isNewRecord() && g_form.getValue('caller_id') == '') {
        g_form.setValue('caller_id', g_user.userID);
    }
    
    // Auto-populate location from user's location
    if (userLocation && g_form.getValue('location') == '') {
        g_form.setValue('location', userLocation);
    }
    
    // Set assignment group based on category
    var category = g_form.getValue('category');
    if (category == 'hardware' && userDept) {
        g_form.setValue('assignment_group', 'Hardware Support');
    }
}
Configuration:
  • Add to Incident form
  • Ensure user has read access to sys_user table
  • Test with different user roles
form-automationuser-contextincident
Business Rules (1)
Auto-assign Tasks Based on Priority
Automatically assign tasks to appropriate groups based on priority and category
Type: before Table: task ID: br001
// Auto-assignment business rule
(function executeRule(current, previous /*null when async*/) {
    
    // Only run on insert and when assignment_group is empty
    if (!current.isNewRecord() && !current.assignment_group.changes()) {
        return;
    }
    
    var priority = current.getValue('priority');
    var category = current.getValue('category');
    
    // High priority items go to specialized teams
    if (priority == '1' || priority == '2') {
        if (category == 'software') {
            current.assignment_group = getGroupSysId('Application Support');
        } else if (category == 'hardware') {
            current.assignment_group = getGroupSysId('Hardware Support');
        } else {
            current.assignment_group = getGroupSysId('IT Service Desk');
        }
    }
    
    function getGroupSysId(groupName) {
        var gr = new GlideRecord('sys_user_group');
        gr.addQuery('name', groupName);
        gr.query();
        if (gr.next()) {
            return gr.getUniqueValue();
        }
        return '';
    }
    
})(current, previous);
Configuration:
  • Table: task
  • When: before
  • Order: 100
  • Active: true
auto-assignmentpriorityworkflow
Script Includes (2)
REST API Integration Helper
Centralized utility for making outbound REST API calls with error handling and logging
Type: script_include ID: si001
var RESTIntegrationHelper = Class.create();
RESTIntegrationHelper.prototype = {
    initialize: function() {
        this.timeout = 30000; // 30 seconds default timeout
        this.maxRetries = 3;
    },
    
    /**
     * Make a REST API call with built-in retry logic and error handling
     * @param {Object} config - Configuration object
     * @param {string} config.url - API endpoint URL
     * @param {string} config.method - HTTP method (GET, POST, PUT, DELETE)
     * @param {Object} config.headers - Request headers
     * @param {string} config.body - Request body (for POST/PUT)
     * @param {number} config.timeout - Request timeout in milliseconds
     * @returns {Object} Response object with status, body, and headers
     */
    makeRequest: function(config) {
        var request = new sn_ws.RESTMessageV2();
        request.setEndpoint(config.url);
        request.setHttpMethod(config.method || 'GET');
        
        // Set headers
        if (config.headers) {
            for (var header in config.headers) {
                request.setRequestHeader(header, config.headers[header]);
            }
        }
        
        // Set body for POST/PUT requests
        if (config.body && (config.method == 'POST' || config.method == 'PUT')) {
            request.setRequestBody(config.body);
        }
        
        // Set timeout
        request.setHttpTimeout(config.timeout || this.timeout);
        
        var response;
        var attempt = 0;
        var maxRetries = config.maxRetries || this.maxRetries;
        
        do {
            try {
                response = request.execute();
                
                // Log the request
                this._logRequest(config, response, attempt);
                
                // Check if we should retry
                if (this._shouldRetry(response) && attempt < maxRetries) {
                    attempt++;
                    gs.sleep(1000 * attempt); // Exponential backoff
                    continue;
                }
                
                return {
                    statusCode: response.getStatusCode(),
                    body: response.getBody(),
                    headers: response.getHeaders(),
                    errorMessage: response.getErrorMessage(),
                    success: response.getStatusCode() >= 200 && response.getStatusCode() < 300
                };
                
            } catch (ex) {
                gs.error('REST API Error on attempt ' + (attempt + 1) + ': ' + ex.getMessage());
                
                if (attempt >= maxRetries) {
                    return {
                        statusCode: 0,
                        body: null,
                        headers: {},
                        errorMessage: ex.getMessage(),
                        success: false
                    };
                }
                attempt++;
                gs.sleep(1000 * attempt);
            }
        } while (attempt <= maxRetries);
    },
    
    /**
     * Determine if request should be retried based on status code
     */
    _shouldRetry: function(response) {
        var statusCode = response.getStatusCode();
        return statusCode >= 500 || statusCode == 429 || statusCode == 408;
    },
    
    /**
     * Log REST API requests for debugging and monitoring
     */
    _logRequest: function(config, response, attempt) {
        var logMessage = 'REST API Call - ' + config.method + ' ' + config.url + 
                        ' - Status: ' + response.getStatusCode() + 
                        ' - Attempt: ' + (attempt + 1);
        
        if (response.getStatusCode() >= 400) {
            gs.warn(logMessage + ' - Error: ' + response.getErrorMessage());
        } else {
            gs.info(logMessage);
        }
    },
    
    type: 'RESTIntegrationHelper'
};
Configuration:
  • Create new Script Include
  • Name: RESTIntegrationHelper
  • Client callable: false
  • Active: true
  • Application: Global
rest-apiintegrationerror-handlingretry-logicutility
CMDB Data Quality Manager NEW
Automated cleanup and validation utility for CMDB Configuration Items with comprehensive reporting
Type: script_include ID: si002
var CMDBDataQualityManager = Class.create();
CMDBDataQualityManager.prototype = {
    initialize: function() {
        this.processedCount = 0;
        this.updatedCount = 0;
        this.duplicatesFound = 0;
        this.orphanedCount = 0;
        this.maxRecordsPerRun = 1000; // Batch processing limit
        this.dryRun = false; // Set to true for testing
    },
    
    /**
     * Main cleanup method - orchestrates all cleanup operations
     * @param {Object} options - Configuration options
     * @param {boolean} options.dryRun - Preview mode without making changes
     * @param {number} options.batchSize - Records to process per batch
     * @param {Array} options.ciClasses - Specific CI classes to process
     * @returns {Object} Cleanup summary report
     */
    performCleanup: function(options) {
        options = options || {};
        this.dryRun = options.dryRun || false;
        this.maxRecordsPerRun = options.batchSize || 1000;
        
        var startTime = new GlideDateTime();
        gs.info('CMDB Cleanup started at ' + startTime.getDisplayValue() + 
                (this.dryRun ? ' (DRY RUN MODE)' : ''));
        
        var report = {
            startTime: startTime.getDisplayValue(),
            dryRun: this.dryRun,
            operations: [],
            summary: {}
        };
        
        try {
            // 1. Clean up orphaned relationships
            report.operations.push(this._cleanupOrphanedRelationships());
            
            // 2. Remove duplicate CIs
            report.operations.push(this._removeDuplicateCIs(options.ciClasses));
            
            // 3. Update stale CI status
            report.operations.push(this._updateStaleCIStatus());
            
            // 4. Validate and fix missing mandatory fields
            report.operations.push(this._validateMandatoryFields(options.ciClasses));
            
            // 5. Clean up old discovery data
            report.operations.push(this._cleanupOldDiscoveryData());
            
        } catch (ex) {
            gs.error('CMDB Cleanup error: ' + ex.getMessage());
            report.error = ex.getMessage();
        }
        
        var endTime = new GlideDateTime();
        report.endTime = endTime.getDisplayValue();
        report.duration = gs.dateDiff(startTime.getNumericValue(), endTime.getNumericValue(), true);
        
        // Generate summary
        report.summary = this._generateSummary(report.operations);
        
        // Log completion
        gs.info('CMDB Cleanup completed. Duration: ' + report.duration + 
                '. Processed: ' + this.processedCount + 
                ', Updated: ' + this.updatedCount);
        
        return report;
    },
    
    /**
     * Remove orphaned CI relationships
     */
    _cleanupOrphanedRelationships: function() {
        var operation = {
            name: 'Orphaned Relationships Cleanup',
            processed: 0,
            removed: 0,
            errors: []
        };
        
        try {
            var gr = new GlideRecord('cmdb_rel_ci');
            gr.addQuery('parent', '');
            gr.addOrCondition('child', '');
            gr.setLimit(this.maxRecordsPerRun);
            gr.query();
            
            while (gr.next()) {
                operation.processed++;
                
                if (!this.dryRun) {
                    gr.deleteRecord();
                    operation.removed++;
                }
                
                // Yield every 100 records to prevent timeout
                if (operation.processed % 100 === 0) {
                    gs.sleep(100);
                }
            }
            
        } catch (ex) {
            operation.errors.push(ex.getMessage());
        }
        
        this.processedCount += operation.processed;
        this.updatedCount += operation.removed;
        
        return operation;
    },
    
    /**
     * Identify and remove duplicate CIs based on name and class
     */
    _removeDuplicateCIs: function(ciClasses) {
        var operation = {
            name: 'Duplicate CIs Removal',
            processed: 0,
            duplicates: 0,
            errors: []
        };
        
        try {
            var classesToProcess = ciClasses || ['cmdb_ci_server', 'cmdb_ci_computer', 'cmdb_ci_service'];
            
            for (var i = 0; i < classesToProcess.length; i++) {
                var className = classesToProcess[i];
                
                // Find duplicates using aggregate query
                var ga = new GlideAggregate(className);
                ga.addAggregate('COUNT', 'name');
                ga.addHaving('COUNT', '>', '1');
                ga.addNotNullQuery('name');
                ga.groupBy('name');
                ga.query();
                
                while (ga.next()) {
                    var duplicateName = ga.getValue('name');
                    this._processDuplicates(className, duplicateName, operation);
                    
                    if (operation.processed >= this.maxRecordsPerRun) {
                        break;
                    }
                }
            }
            
        } catch (ex) {
            operation.errors.push(ex.getMessage());
        }
        
        return operation;
    },
    
    /**
     * Process duplicate CIs for a given name
     */
    _processDuplicates: function(className, name, operation) {
        var gr = new GlideRecord(className);
        gr.addQuery('name', name);
        gr.orderByDesc('sys_updated_on'); // Keep the most recently updated
        gr.query();
        
        var keepFirst = true;
        while (gr.next()) {
            operation.processed++;
            
            if (keepFirst) {
                keepFirst = false;
                continue; // Keep the first (most recent) record
            }
            
            // This is a duplicate - remove it
            if (!this.dryRun) {
                gr.deleteRecord();
                operation.duplicates++;
            }
        }
    },
    
    /**
     * Update CIs with stale status based on last discovery
     */
    _updateStaleCIStatus: function() {
        var operation = {
            name: 'Stale CI Status Update',
            processed: 0,
            updated: 0,
            errors: []
        };
        
        try {
            // Find CIs not updated by discovery in 90 days
            var staleDate = new GlideDateTime();
            staleDate.addDaysLocalTime(-90);
            
            var gr = new GlideRecord('cmdb_ci');
            gr.addQuery('last_discovered', '<', staleDate);
            gr.addQuery('install_status', '1'); // Installed
            gr.setLimit(this.maxRecordsPerRun);
            gr.query();
            
            while (gr.next()) {
                operation.processed++;
                
                if (!this.dryRun) {
                    gr.setValue('install_status', '7'); // Retired
                    gr.setValue('comments', 'Auto-retired due to stale discovery data');
                    gr.update();
                    operation.updated++;
                }
                
                if (operation.processed % 50 === 0) {
                    gs.sleep(100);
                }
            }
            
        } catch (ex) {
            operation.errors.push(ex.getMessage());
        }
        
        return operation;
    },
    
    /**
     * Validate and fix missing mandatory fields
     */
    _validateMandatoryFields: function(ciClasses) {
        var operation = {
            name: 'Mandatory Fields Validation',
            processed: 0,
            updated: 0,
            errors: []
        };
        
        try {
            var classesToProcess = ciClasses || ['cmdb_ci_server'];
            
            for (var i = 0; i < classesToProcess.length; i++) {
                var className = classesToProcess[i];
                
                var gr = new GlideRecord(className);
                gr.addQuery('name', '');
                gr.addOrCondition('operational_status', '');
                gr.setLimit(this.maxRecordsPerRun / classesToProcess.length);
                gr.query();
                
                while (gr.next()) {
                    operation.processed++;
                    var updated = false;
                    
                    if (!this.dryRun) {
                        // Fix empty name
                        if (gr.getValue('name') == '') {
                            gr.setValue('name', 'Unknown-' + gr.getUniqueValue().substring(0, 8));
                            updated = true;
                        }
                        
                        // Fix empty operational status
                        if (gr.getValue('operational_status') == '') {
                            gr.setValue('operational_status', '1'); // Operational
                            updated = true;
                        }
                        
                        if (updated) {
                            gr.update();
                            operation.updated++;
                        }
                    }
                }
            }
            
        } catch (ex) {
            operation.errors.push(ex.getMessage());
        }
        
        return operation;
    },
    
    /**
     * Clean up old discovery data and logs
     */
    _cleanupOldDiscoveryData: function() {
        var operation = {
            name: 'Old Discovery Data Cleanup',
            processed: 0,
            removed: 0,
            errors: []
        };
        
        try {
            // Remove discovery logs older than 6 months
            var cutoffDate = new GlideDateTime();
            cutoffDate.addMonthsLocalTime(-6);
            
            var gr = new GlideRecord('discovery_log');
            gr.addQuery('sys_created_on', '<', cutoffDate);
            gr.setLimit(this.maxRecordsPerRun);
            gr.query();
            
            while (gr.next()) {
                operation.processed++;
                
                if (!this.dryRun) {
                    gr.deleteRecord();
                    operation.removed++;
                }
                
                if (operation.processed % 100 === 0) {
                    gs.sleep(100);
                }
            }
            
        } catch (ex) {
            operation.errors.push(ex.getMessage());
        }
        
        return operation;
    },
    
    /**
     * Generate cleanup summary report
     */
    _generateSummary: function(operations) {
        var summary = {
            totalProcessed: 0,
            totalUpdated: 0,
            operationCount: operations.length,
            hasErrors: false
        };
        
        for (var i = 0; i < operations.length; i++) {
            var op = operations[i];
            summary.totalProcessed += op.processed || 0;
            summary.totalUpdated += (op.removed || 0) + (op.updated || 0) + (op.duplicates || 0);
            
            if (op.errors && op.errors.length > 0) {
                summary.hasErrors = true;
            }
        }
        
        return summary;
    },
    
    type: 'CMDBDataQualityManager'
};
Configuration:
  • Create new Script Include
  • Name: RESTIntegrationHelper
  • Client callable: false
  • Active: true
  • Application: Global
rest-apiintegrationerror-handlingretry-logicutility
UI Actions (1)
Bulk Update Assignment Group
Allow bulk updating of assignment group for selected records in list view
Type: list_button Table: incident ID: ui001
// Client-side script
if (confirm('Update assignment group for selected records?')) {
    var selectedRows = g_list.getChecked();
    if (selectedRows.length == 0) {
        alert('Please select at least one record.');
        return;
    }
    
    var newGroup = prompt('Enter new assignment group name:');
    if (newGroup) {
        gsftSubmit(null, g_list.getFixedQuery(), 'bulk_update_assignment', 
                  'sysparm_assignment_group=' + encodeURIComponent(newGroup) + 
                  '&sysparm_checked_items=' + selectedRows);
    }
}
Configuration:
  • Table: incident
  • Action name: bulk_update_assignment
  • Form button: false
  • List button: true
  • Show insert: false
  • Show update: false
bulk-operationslist-viewassignment