Skip to main content

Posts

Showing posts from May, 2019

UI Action Trigger New Approval

this can be used to modify the approver and re-send a new email restricted to sys admins (or whatever role desired) name: Trigger New Approval condition: current.state=='requested' && gs.hasRole("admin") script: function trigger_new_approval(){ var res=confirm('Have you updated the approver and are happy to trigger a new approval notification?'); if (res){ alert('re-sending approval to ' + g_form.getValue('approver')); gsftSubmit(null, g_form.getFormElement(), 'resend_email'); //MUST call the 'Action name' set in this UI Action } } if (typeof window == 'undefined'){ resend_approval(); } function resend_approval(){ var grAppr=new GlideRecord('sysapproval_approver'); var grUser=new GlideRecord('sys_user'); if (grUser.get('sys_id', current.approver)){ if (!gs.nil(grUser.email)){ gs.eventQueue('approval.inserted', current, grUser.sys_id, gr

UI Action Test Load 2 records

instead of the ootb test load records which requires certain privileged roles to access, create a new one which can be restricted to the itil role makes a httprequest call to the out of the box java processor (which itself cannot be edited) UI Action: "Test Load 2 Records (ServiceDesk)" table: data source (sys_data_source) condition: current.import_set_table_name=='u_<whatever table name>' && gs.hasRole('<whatever role>service_desk'); code: -------- -------- current.update(); try{ //--run the processor this way, so that service desk do not need import_transformer role to do this var url=gs.getProperty('glide.servlet.uri')+'sys_import.do'; var request=new GlideHTTPRequest(url); request.setBasicAuth(gs.getProperty('sd.uid'), gs.getProperty('sd.pwd')); request.addHeader('Accept', 'application/json'); request.addParameter('import_source', 'data_source

Locate trigger action from workflow

ui action table: wf_executing name: "Locate Matching sys_trigger entry" //--server side option: var url='/sys_trigger_list.do?sysparm_query=nameLIKE' + current.sys_id + '&sysparm_list_mode=grid'; action.setRedirectURL ( url ) ; > go to it and update the date to a few seconds from now ------------- how to enable the worklflow context ui action on the ticket: > do an insert and stay on the workflow context ui action, adding in the table it should display on (for example, hr case) script can be modified to: var url= '/wf_context_list.do?sysparm_query=id%3D'+ current.sys_id; action.setRedirectURL(url); clicking this should take you to the wf_executing entries (under the related tab), click into the wait block entry and you should see the new Ui action

kill old workflows

 this article now superceded by a better solution, go to: http://www.cloudminus89.com/2022/07/servicenow-kill-workflows-executing.html   var sQuery="active=true^started<javascript:gs.beginningOfLast12Months()"; var grWF=new GlideRecord('wf_context'); grWF.addQuery(sQuery); grWF.query(); gs.print(grWF.getRowCount()); while (grWF.next()){   var grTask=new GlideRecord('task');   grTask.addInactiveQuery();   grTask.addQuery('sys_id', grWF.id);   grTask.query();   if (grTask.next()){      gs.print('ticket ' +  grTask.number);      grWF.state='cancelled';      grWF.active=false;      var cancel_sysid=grWF.update();      gs.print(cancel_sysid + ': cancelled workflow context');      //break;   } }

Attach a file to email for users with no roles

attach a file to a published KB article then link to the attachment from an email notification script: (function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template, /* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action, /* Optional GlideRecord */ event) { //--as per https://docs.servicenow.com/bundle/london-servicenow-platform/page/administer/notification/concept/c_AttachingDocsToANotification.html var url='/sys_attachment.do?sys_id=1d9ab0f31b5db300cee5ed7cee4bcb08&sysparm_viewer_table=kb_knowledge&sysparm_viewer_id=497a30f31b5db300cee5ed7cee4bcb81'; //--had to add the attachment to a KB article in order to be able to allow non-roled users to access the attachment template.print ('<a href="' + url + '">Click to download the 6th floor plan</a>'); })(current, template, email, email_action, event);

ServiceNow variable advanced reference qualifier example

ServiceNow variable advanced reference qualifier example javascript: 'sys_idIN'+new hld_hr_functions().getHRRefQual('hr_openedfor_returner'); *************** script incl funcs: getHRRefQual: function(refQualID){ //--build up ref quals var sRefQualQuery=""; if (refQualID=='hr_openedfor_returner'){ //--called from here: /nav_to.do?uri=item_option_new.do?sys_id=271a5f781b766f00cee5ed7cee4bcb60 //sRefQualQuery="nameISNOTEMPTY^u_technical_user=false^" + this._DormantAccountUsers(); sRefQualQuery=this._DormantAccountUsers(); } return sRefQualQuery; }, _DormantAccountUsers: function(){ //--use below to query dormant accounts table var grDorm=new GlideRecord('u_dormant_accounts'); var sQuery='u_user.nameISNOTEMPTY^u_user.u_technical_user=false^u_user.last_login_time<javascript:gs.beginningOfLast3Months()^ORu_user.last_login_timeISEMPTY'; grDorm.addQuery(sQuery); //grDorm.addQ

Get bits from the URL

get entire url: var instanceURL = GlideTransaction.get().getRequest().getHeader("referer"); get bit of a url: var surl='https://glddev.service-now.com/hr/case_detail.do?sysparm_case=210ebaf74f82ef00aaa42c518110c7a9&sysparm_newcase=true&sysparm_nameofstack=250efaf74f82ef00aaa42c518110c705'; //--bit between 'sysparm_newcase' and '&' var testRE = surl.match("sysparm_newcase=(.*)&"); gs.print(testRE);

XML Parse example (on ecc queue table)

var grECCQ=new GlideRecord('ecc_queue'); //var sysid='8929f7ec4fed2700ddd0ecd18110c765'; sysid='2d616f684f2d2700ddd0ecd18110c71c'; if (grECCQ.get('sys_id', sysid)){    var pl_xml=new XMLDocument(grECCQ.payload); var inodes= pl_xml.getNodes("//parameter"); for (x=0;x<inodes.length;x++){ //gs.print(inodes.item(x).getAttribute('name') + '::: ' + inodes.item(x).getAttribute('value')); var sname=inodes.item(x).getAttribute('name'); //if (sname=='sequence'){ if (sname=='error_string'){    sValue=inodes.item(x).getAttribute('value');   gs.print('sValue=' + sValue);    if (sValue!=''){         gs.print('ERR!!');    } } } var sErrChk2=pl_xml.getNodeText("//error"); gs.print(sErrChk2); }

URL link in addInfoMessage

var ga=new GlideAjax('gld_HR_ajax'); ga.addParam('sysparm_name', 'checkEmployeeNumber_hrProfile'); ga.addParam('sysparm_hrprofilenumber', g_form.getValue('number')); ga.addParam('sysparm_employeenumber', newValue); ga.getXMLAnswer(function(answer) { if (answer!='undefined' && answer!=''){ var navURL="<a style='text-decoration:underline;color:blue' href=hr_profile.do?sysparm_query=number=" + answer + ">" + answer + "</a><img width='3' src='images/s.gif'/>"; var sMsg='The employee number entered already exists on another HR Profile ' + navURL; //alert(sMsg); g_form.showErrorBox('employee_number', 'error - please check'); g_form.addInfoMessage(sMsg); } });

UI Macro: attachment file for record producer

<?xml version="1.0" encoding="utf-8" ?>  <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">  <a onclick="saveCatAttachment(gel('sysparm_item_guid').value, 'new_call')">  <img title="Attachments..." height="16" src="images/icons/attachment.gifx" border="0" width="16"/> Click here</a>  to attach items to this request. </j:jelly>

UI Macro: link to CIs assigned to user

<?xml version="1.0" encoding="utf-8" ?> <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">   Assets currently assigned to this user: <a href='' onclick="event.preventDefault();gdprRef('${gs.getMessage('gdpr')}');">click here</a> <script>   function gdprRef(url){   var req_for=g_form.getValue('var_requested_for');   if (req_for!=''){ var dialog = new GlideDialogWindow('show_list'); dialog.setTitle('Assets assigned to user'); dialog.setPreference('table', 'cmdb_ci_list'); dialog.setPreference('sysparm_view', 'default'); var query = 'assigned_to=' + req_for; dialog.setPreference('sysparm_query', query);     dialog.render();   } }

deal with leading / trailing spaces

var ciclass='u_cmdb_ci_laptops'; //ciclass='u_smart_devices'; var querytorun='nameISNOTEMPTY^install_status!=14'; //var querytorun='nameISEMPTY'; var gr=new GlideRecord(ciclass); gr.addQuery(querytorun); gr.query(); while (gr.next()){ if (startsOrEndsWithWhitespace(gr.name)){ //if (startsOrEndsWithWhitespace(gr.sys_id)){//--for empty name query gs.print(gr.name); //gs.print(gr.sys_id); //--for empty name query } } function startsOrEndsWithWhitespace(str){ return /^\s|\s$/.test(str); }

check if time out of hours

--business hours in this example 8am-5pm: var gdt=gs.nowDateTime(); var gdtArr= gdt.split(' '); var timeArr=gdtArr[1].split(':'); //vat currenttime=parseInt(timeArr[0]); gs.print(timeArr[0]); if (timeArr[0]>=17 || timeArr[0]<8){    gs.print('out of hours'); }

search for a ticket based on variable value

var max_records=3; var sQuery='active=false^sys_created_onONLast 60 days@javascript:gs.beginningOfLast60Days()@javascript:gs.endOfLast60Days()'; var sShortDescrQuery='^short_descriptionLIKEchange'; var variableValueToCheck='true'; //--the more refined the query, the quicker it will run //--run it in a test environment first! //getMatchingTickets('hr_case', '', 'ict_required', '', 'Yes', sQuery, max_records); //getMatchingTickets('hr_case', '', '', 'ICT Required?', 'Yes', sQuery, max_records); //getMatchingTickets('hr_case', '', 'loan_secondment_return', '', 'true', sQuery, max_records); //getMatchingTickets('hr_case', '', 'maternity_career_return', '', 'true', sQuery, max_records); //getMatchingTickets('hr_case', '', 'loan_secondment_start', '', 'true', s

re-start a workflow

var gr = new GlideRecord('wf_context'); gr.addQuery("id", current.sys_id); gr.addInactiveQuery(); gr.addQuery("name","HR case placed on hold"); gr.query(); //while (gr.next()){ if (gr.next()){ /*gs.addInfoMessage(gr.active); if (gr.active && current.state!=previous.state){ //--cancel the old workflow gs.addInfoMessage('cancelling previous on hold workflow'); var wf = new Workflow(); wf.cancel(gr); }*/ gs.addInfoMessage('restarting "HR case placed on hold" workflow'); var wflw = new Workflow(); wflw.startFlow(wflw.getWorkflowFromName('HR case placed on hold'), current, 'update'); //break; }

ServiceNow record producer variables in a business rule

ServiceNow record producer variables in a business rule //--new code, RDS Dec2018 g_scratchpad.rp_sysid=''; var producerVars_allVars = new GlideRecord('question_answer'); //var allVars = []; producerVars_allVars.addQuery('table_sys_id', current.sys_id); //--Exclude Label and Container variables producerVars_allVars.addQuery('question.type', '!=', 11); producerVars_allVars.addQuery('question.type', '!=', 19); producerVars_allVars.addQuery('question.type', '!=', 20); //--note: UI Macro is question.type=14 producerVars_allVars.query(); //gs.addInfoMessage(current.producer.sys_id); while(producerVars_allVars.next()){ //gs.addInfoMessage(producerVars_allVars.question.type + ' ' + producerVars_allVars.question.name.toString()  + ' ' + producerVars_allVars.value.toString()); if (producerVars_allVars.question.name.toString()=='hr_rp_sysid'){

notify when MID switched on in non prod

via event. buiness rule (ecc_agent table) (function executeRule(current, previous /*null when async*/) { //--double check that the environment is preprod var instURL=gs.getProperty('glide.servlet.uri'); if (instURL.toLowerCase()=='https://<>preprod.service-now.com/'){ gs.eventQueue('preprod.mid.switchedon', current,'',''); //--now, queue the event on prod also so the email can actually fire try{ //--run the processor this way, so that service desk do not need import_transformer role to do this var url='https://<>prod.service-now.com/preprodmid.do'; var request=new GlideHTTPRequest(url); request.setBasicAuth(gs.getProperty('proc_uid'), gs.getProperty('proc_uid'));//--user in SN and credentials replicated to sys properties request.addHeader('Accept', 'application/json'); //request.addParameter('', ''); var response=request.get();

Set a record producer variable

var si=new hld_hr_functions(); //setRPVariable: function(table name, ticket sysid, variable label, variable name, new variable value){ si.setRPVariable('hr_change', 'f886c7881bf4b300cee5ed7cee4bcb15','','snc_located_in_ad', 'BLAH'); script include code: setRPVariable: function(table, ticketSYSID, optVariableName, optVariableName_db, new_value){ //--update a record producer variable value //--called from a HR workflow (HR Leaver Returning with Old Trent ID) //--reference: //--https://docs.servicenow.com/bundle/london-application-development/page/script/server-scripting/concept/c_ScriptableServiceCatalogVariables.html var gr= new GlideRecord(table); gr.addQuery('sys_id', ticketSYSID); gr.query(); var vars=[]; if (gr.next()) { for (var prop in gr.variables) { if (gr.variables.hasOwnProperty(prop) ){ var variable =  gr.variables[prop].getDisplayValue(); var v = gr.variables[prop];

How to skip a workflow timer (using admin only ui action)

https://community.servicenow.com/community?id=community_question&sys_id=232ec3eddb9cdbc01dcaf3231f9619fa go to sys_trigger look for name contains WFTimer try and locate by name contains sysid of the active timer record; failing that, search for the matching next action field on date/time stamp update the sys_trigger.next action to a few seconds from now watch the workflow execute --- UI ACTIONS ON wf_executing table: ---non-client ui action redirect: var url='/sys_trigger_list.do?sysparm_query=nameLIKE' + current.sys_id + '&sysparm_list_mode=grid'; action.setRedirectURL ( url ) ; UI Action - client script version: client=true onclick=go_to_trigger() function go_to_trigger(){ //alert('hello'+g_form.getUniqueValue()); var grTr=new GlideRecord('sys_trigger'); grTr.addQuery('nameLIKE' + g_form.getUniqueValue()); grTr.query(); if (grTr.next()){ //alert('in GR'); var url = new GlideURL(&#

fix script to kill old ageing workflows

 this article now superceded by a better solution, go to: http://www.cloudminus89.com/2022/07/servicenow-kill-workflows-executing.html   these workflows may be stuck in limbo on tickets since closed var sQuery="active=true^started<javascript:gs.dateGenerate('2018-01-02','00:00:00')"; var grWF=new GlideRecord('wf_context'); grWF.addQuery(sQuery); grWF.query(); gs.print(grWF.getRowCount()); while (grWF.next()){   var grTask=new GlideRecord('task');   grTask.addInactiveQuery();   grTask.addQuery('sys_id', grWF.id);   grTask.query();   if (grTask.next()){      gs.print('ticket ' +  grTask.number);      grWF.state='cancelled';      grWF.active=false;      var cancel_sysid=grWF.update();      gs.print(cancel_sysid);      //break;   } }

Enforce max number of rows displayed in lists

might be needed for performance reasons to do this: go to system properties > UI properties > edit the "Items per page' drop-down options (comma separated, no spaces):" box fix script to tidy up old user preferences: -------------------------------------------------- updatePreferences('name=rowcount^value>=20^value!=20'); updatePreferences('value=100'); function updatePreferences(sQuery){ var grUserPreferences=new GlideRecord('sys_user_preference'); grUserPreferences.addQuery(sQuery); grUserPreferences.query(); gs.print('TOTAL: ' + grUserPreferences.getRowCount()); //grUserPreferences.setValue('rowcount',  '20'); //grUserPreferences.updateMultiple(); while (grUserPreferences.next()){ grUserPreferences.value='20'; grUserPreferences.update(); gs.print(grUserPreferences.user.name + ' updated'); } }

Copy attachment to target ticket

if (!gs.nil(sysID)){ var sysAttachment = new GlideRecord('sys_attachment'); sysAttachment.addQuery('table_sys_id',current.sys_id); sysAttachment.query(); var bCopy=false; while(sysAttachment.next()){ gs.addInfoMessage("Copying attachment " + sysAttachment.file_name + " to target ticket " + gr.number +" [table:" +  ctype + "]"); bCopy=true; } if (bCopy){ GlideSysAttachment.copy('new_call',current.sys_id,ctype,gr.sys_id);//--copies them all in one go } }

processor: fire an email when a MID is switched on in sub production

Solution for when email sending is switched off in sub production but switching on a MID could target prod systems (e.g. LDAP), a web service call is made to invoke a prod processor to queue the notification from prod using a processor call on prod business rule - RUNS ON SUBPROD: send alert when MID server switched on in UAT - processor via glidehttpsrequest ---------- (function executeRule(current, previous /*null when async*/) { //--double check that the environment is preprod var instURL=gs.getProperty('glide.servlet.uri'); if (instURL.toLowerCase()=='https://preprod.service-now.com/'){ gs.eventQueue('.preprod.mid.switchedon', current,'',''); //--now, queue the event on prod also so the email can actually fire try{ //--run the processor this way, so that the notification can be sent from production (business rule runs in preprod where notifications are switched off) var url='https://<prod>.servic

widget: prod changes

  HTML ___________________________ <!--span option:--> <h1>ServiceNow Production Changes</h1> <h2>Listed in order of most recent</h2> <h3>{{c.numberOfTickets}} change requests</h3> <div style="border: 2px solid black">   <div ng-repeat="item in c.chgsList" ng-class="'color' + ($index % 2)">   <span><b><a href="change_request.do?sysparm_query=number={{item.number}}">{{item.number}}</a></b></span><span>{{item.short_description}}</span><span> (deployed on: {{item.start_date}})</span>    <span class="formattxt">{{item.description}}</span>   <br/><br/> </div> </div>     <!--table option:--><!-- <div class="chgwrap" >   <h1>ServiceNow Production Changes</h1>   <h2>Listed in order of most recent</h2>     <tabl

widget: get RITMs linked to HR case

RITMs generated from HR workflow via an order guide, where a common hr_case_ref variable is in use on the RITMs and populated via the HR workflow (passed from order guide to child RITM)   HTML ___________________________ <title>Check RITMs for the HR Case</title> <div class='container'> <!-- your widget template -->  <table width="100px" border="0">      <tr><td>      <br/>      <label>Enter HR Case number here:</label>      <input value='HRM0000126' id='hr_case'/>      <br/>    </td></tr>    <tr><td>      <label>Enter order guide name here (optional):</label>      <input id='order_guide' ng-model="inputOG">        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;      <button ng-click="blankOrderGuideField()" class="btn b

generate trigger from business rule

function onAfter(current, previous) {   // Create trigger to set HR Case to Work in Progress   // when On Hold review date passes   try {     // Search for any pre-existing triggers     var trigger = new GlideRecord("sys_trigger");     trigger.addQuery("document_key", current.sys_id);     trigger.addQuery('name', 'STARTSWITH', 'HR Case OnHold Review - ');     trigger.query();     while (trigger.next()) {       // Remove trigger(s) for this document       trigger.deleteRecord();     }         // Set review date and set time to start of day     var requestDate = new GlideDateTime(current.u_review_date);     var datestr = requestDate.getLocalDate().toString();     if (requestDate.isDST()) {       requestDate.setInitialValue( datestr + " 09:00:00" );     } else {       requestDate.setInitialValue( datestr + " 08:00:00" );     }     gs.log('Set On Hold Review Date for ' + current.number + ' to

hide options label

//--warning - not service portal friendly! function hideLabels(){ //--hide the annoying 'options' labels! (may need to rework this later for Service Portal) //$('label_IO:d6664818dba36300e5d0d8e74b9619ad').hide(); var  labels = document.getElementsByTagName('span'); var x; for (x in labels) { if (labels[x].innerText=='Options'){ ////labels[x].style.display = "none"; labels[x].hide(); } } }

GlideAggregate: duplicates

var ga= new GlideAggregate('task'); ga.addAggregate('COUNT', 'number'); ga.addHaving('COUNT', '>', 1); ga.query();     var dupls=[];     dupls.push("The following " + ga.getRowCount() + " records are duplicate on number ");   while (ga.next()) {         var DuplLine=ga.getAggregate('COUNT', 'number') + " => " + ga.getElement('number');      dupls.push(DuplLine); gs.print(DuplLine);  }

Service Portal $sp.getRecordDisplayValues

SERVER SCRIPT: data.list = [] var gr=new GlideRecord('incident'); .. while (gr.next()){    var incident={}    $sp.getRecordDisplayValues (incident, gr, 'name,state, ... ');    data.list.push (incident); } HTML: <th ng-model="c.searchTerm"> <tr ng-repeat="inc in c.data.list | filter: c.searchTerm"> <td>{{inc.number}} <td>{{inc.state}} <td> ...

widget with table summary (count)

looks like this HTML <form>   <!-- Step 2. Display initial data from the server -->   <div class="panel panel-default">     <!-- Default panel contents -->     <div class="panel-heading">Table Summary: Registrations</div>     <!-- List group -->     <ul class="list-group">       <li class="list-group-item"           ng-repeat="s in c.data.summary"           ng-click="c.selectAg(s)">{{ s.agName }}         <span class="badge">{{ s.agCount }}</span>       </li>     </ul>   </div>   <!-- Step 5. Display customized content based on user input -->  <pre>{{c.data.records | json}}</pre> </form> <!-- copied from:  https://getbootstrap.com/docs/3.3/components/#panels-list-group   --> CSS .badge{background:red;} CLIENT   function() { /* widget controller */ var c =

debug widgets

select ctrl-right click (windows machine) to bring up the debug dialog can also output the data object in html to debug {{  data | json }}

GlideRecord setValue

setValue(String name, Object value) Sets the specified field to the specified value. Normally a script would do a direct assignment, for example,  gr.category = value . However, if in a script the element name is a variable, then  gr.setValue(elementName, value)  can be used. When setting a value, ensure the data type of the field matches the data type of the value you enter. This method cannot be used on journal fields. If the value parameter is null, the record is not updated, and an error is not thrown https://developer.servicenow.com/app.do#!/api_doc?v=madrid&id=r_GlideRecord-setValue_String_Object

Service Portal AngularJS and Bootstrap references

https://getbootstrap.com/docs/3.4/css/ https://docs.angularjs.org/api https://angular-ui.github.io/ https://angular-ui.github.io/bootstrap/ ------- For those people who want to have more in-depth coverage of scoping and $scope - controllerAs; please consult the following URLs: Understanding AngularJS | Pluralsight AngularJS: MVC implementation | Pluralsight AngularJS - Controllers: The Comprehensive How To | Pluralsight | Pluralsight AngularJS Models: The Scoop on Scopes | Pluralsight | Pluralsight

service portal widgets: use pre to display data.events as a JSON object

enter anywhere in the HTML template of the widget  <pre>{{c.data.events}}</pre> (server script populates the data.events)

Knowledge 19 kicks off...