CA Service Desk provides a SOAP web service that allows provides you with queries and operations into the Service Desk software. The WSDL to their API can be found at:
http://YOUR_CA_SERVICE_DESK_SERVER:8080/axis/services/USD_R11_WebService?wsdl
In the following example, I created a Script Include that handles authentication as well as database style queries through the “doSelect” web service function as well as creating activity logs through the “createActivityLog” function.
Before we get into the code I wrote, we need to outline the the SOAP Functions that I created in ServiceNow to connect to CA Service Desk.
Please note, for the full WSDL, feel free to download the CA Service Desk WSDL wsdl document.
I used this WSDL document to create a SOAP Message record which I named “CA Service Desk”.
CA Service Desk Login Service
In order to use the SOAP API, you must first authenticate using the Login Service. The response to this call will return a token that you must use in your subsequent calls into the API.
Here are the ServiceNow SOAP settings I used for Authentication:
SOAP Message Function: login
SOAP Action: Login
SOAP Endpoint: http://YOUR_SERVER:8080/axis/services/USD_R11_WebService
Envelope:
1 2 3 4 5 6 7 8 | <SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://www.ca.com/UnicenterServicePlus/ServiceDesk" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <login xmlns="http://www.ca.com/UnicenterServicePlus/ServiceDesk"> <username xsi:type="xsd:string">${username}</username> <password xsi:type="xsd:string">${password}</password> </login> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
CA Service Desk doSelect Service
This service lets you query the backend database in Service Desk.
SOAP Message Function: doSelect
SOAP Action: doSelect
SOAP Endpoint: http://YOUR_SERVER:8080/axis/services/USD_R11_WebService
Envelope:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://www.ca.com/UnicenterServicePlus/ServiceDesk" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <doSelect xmlns="http://www.ca.com/UnicenterServicePlus/ServiceDesk"> <sid xsi:type="xsd:int">${sid}</sid> <objectType xsi:type="xsd:string">${objectType}</objectType> <whereClause xsi:type="xsd:string">${whereClause}</whereClause> <maxRows xsi:type="xsd:int">${maxRows}</maxRows> <attributes xsi:type="m:ArrayOfString"> ${attributes} </attributes> </doSelect> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
CA Service Desk createActivityLog Service
This web service creates an activity within Service Desk
SOAP Message Function: createActivityLog
SOAP Action: createActivityLog
SOAP Endpoint: http://YOUR_SERVER:8080/axis/services/USD_R11_WebService
Envelope:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://www.ca.com/UnicenterServicePlus/ServiceDesk" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <createActivityLog xmlns="http://www.ca.com/UnicenterServicePlus/ServiceDesk"> <sid xsi:type="xsd:int">${sid}</sid> <creator xsi:type="xsd:string">${creator}</creator> <objectHandle xsi:type="xsd:string">${objectHandle}</objectHandle> <description xsi:type="xsd:string">${description}</description> <logType xsi:type="xsd:string">${logType}</logType> <timeSpent xsi:type="xsd:int">${timeSpent}</timeSpent> <internal xsi:type="xsd:boolean">${internal}</internal> </createActivityLog> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Helper Script Include Library
The following helper functions allow you to leverage the SOAP Messages with very little worry about authentication, roles, etc. It will attempt to perform the operation. If there is no current user token, then it will perform a Login call and then use the token to execute the desired call.
Please note that for this example, the username and password are embedded in the code rather than queried as system properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | var CASDHelper = Class.create(); CASDHelper.prototype = { logSource: "CASD-Integration", initialize: function() { this.logLevel = gs.getProperty("com.snc.integration.usd.loglevel", "3"); }, /* * Authenticates into CA Service Desk and gets a session ID token * There is typically need to call this function manually since primary query functions * will auto detect whether a new sessionID token is required. */ usdLogin:function() { var s = new SOAPMessage('CA Service Desk', 'login'); s.setParameter('username', 'MYUserName'); s.setParameter('password', 'MYPassword'); s.post(); obj = this.getResponse(s); if(obj['soapenv:Body'] && obj['soapenv:Body'].loginResponse && obj['soapenv:Body'].loginResponse.loginReturn) { var sid = obj['soapenv:Body'].loginResponse.loginReturn['#text']; this.log("LOGIN returns a SID of: " + sid); gs.setProperty("com.snc.integration.usd.sid", sid); return sid; } }, /* * Invalidates the current Session ID used by the ServiceNow instance */ usdLogout: function() { var s = new SOAPMessage('CA Service Desk', 'logout'); var sid = gs.getProperty("com.snc.integration.usd.sid",""); s.setParameter('sid', sid); s.post(); gs.setProperty("com.snc.integration.usd.sid",""); }, /* * * Perform a database query against CA ServiceDesk * * PARAMETERS * objType - object being queried (eg. "cr") * query - the actual query string (eg. "ref_num = '5253708') * maxRows - max number of results to return * attributes - Array of attribute names that we want returned in the query * (eg. ["description", "persistent_id"]) * login - (default: false) Force a login first. Do not attempt to reuse an stored sessionID's * Please note: this method, by default, will attempt to perform the action * against the last used session id. If that fails, we will force login * * RETURN * * Single or Array of object with the following members * .Handle - the id of the current record in the table * .Attributes[] - An array of returned attribute name/value pairs * .Attributes[].AttrName - The attribute name * .Attributes[].AttrValue - the value of the attribute * * RETURN OBJECT EXAMPLES * 1) Single Record Response - get First Attribute Value * var object = a.usdDoSelect("cr", "ref_num='5253708'", 10, ["description", "persistent_id"]); * var val = object.Attributes.Attribute[0].AttrValue; * 2) Multiple Reocrd Response - get first record's first Attribute Value * var object = a.usdDoSelect("cr", "ref_num<'5253708'", 10, ["description", "persistent_id"]); * var val = object[0].Attributes.Attribute[0].AttrValue; */ usdDoSelect: function( objType, query, maxRows, attributes, login ){ if(!login){ this.log("Calling USD doSelect -- attempting to reuse a previous SID token"); login = false; } else { this.log("Calling USD doSelect -- forcing a login first"); } var attribs = ""; for(key in attributes){ attribs += "<string xsi:type='xsd:string'>"+attributes[key]+"</string>"; } if(login){ var sid = this.usdLogin(); } else { sid = gs.getProperty("com.snc.integration.usd.sid",""); } if(sid != ""){ var s = new SOAPMessage('CA Service Desk', 'doSelect'); s.setStringParameter('whereClause', query); s.setStringParameter('sid', sid); s.setXMLParameter('attributes', attribs); s.setStringParameter('objectType', objType); s.setStringParameter('maxRows', maxRows); s.post(); var obj = this.getResponse(s); if(this.logLevel >= 3){ JSUtil.logObject(obj); } if( obj['soapenv:Body'] && obj['soapenv:Body'].doSelectResponse && obj['soapenv:Body'].doSelectResponse.doSelectReturn && obj['soapenv:Body'].doSelectResponse.doSelectReturn.UDSObjectList){ var udsObject = obj['soapenv:Body'].doSelectResponse.doSelectReturn.UDSObjectList.UDSObject; if(udsObject){ return udsObject; } } } if( sid == "" || (login==false && this.usdCheckForLoginError(obj))){ return this.usdDoSelect(objType, query, maxRows, attributes, true); } }, /* * Creates a Log entry against a specified record * * PARAMETERS * handle - handle/id of the record we will be logging against * message - the message being posted * login - (default: false) Force a login first. Do not attempt to reuse an stored sessionID's * Please note: this method, by default, will attempt to perform the action * against the last used session id. If that fails, we will force login * * Returns the handle string of the activity log if everything was successful...otherwise empty string * */ usdCreateActivityLog: function(handle, message, login){ if(!login){ this.log("Calling USD createActivityLog -- attempting to reuse a previous SID token"); login = false; } else { this.log("Calling USD createActivityLog -- forcing a login first"); } if(login){ var sid = this.usdLogin(); } else { sid = gs.getProperty("com.snc.integration.usd.sid",""); } if(sid != ""){ var s = new SOAPMessage('CA Service Desk', 'createActivityLog'); s.setParameter('description', message); s.setParameter('timeSpent', '0'); s.setParameter('logType', 'LOG'); s.setParameter('internal', 'False'); s.setParameter('sid', sid); s.setParameter('objectHandle', handle); s.setParameter('creator', 'cnt:65A118F08AB7824F82E88534C0613BEE'); s.post(); var obj = this.getResponse(s); if(this.logLevel >= 3){ JSUtil.logObject(obj); } if( obj['soapenv:Body'] && obj['soapenv:Body'].createActivityLogResponse && obj['soapenv:Body'].createActivityLogResponse.createActivityLogReturn && obj['soapenv:Body'].createActivityLogResponse.createActivityLogReturn.UDSObject){ var udsObject = obj['soapenv:Body'].createActivityLogResponse.createActivityLogReturn.UDSObject; if(udsObject.Handle){ return udsObject.Handle; } } } if( sid == "" || (login==false && this.usdCheckForLoginError(obj))){ return this.usdCreateActivityLog(handle, message, true); } return ""; }, getResponse: function (s,xml){ if(!xml && xml!=false){ xml = true; } var k = 1; var r = s.getResponse(); while(r == null) { this.log("waiting ... " + k + " seconds"); r = s.getResponse(1000); k++; if (k > 330) { this.log("ERROR: Never got a response from web service call through MID Server", 0); break; // service did not respond after 30 tries } } if(xml){ this.log("Got Response of : " +r); r = this.usdRemoveExtraXmlDef(r); this.log("Going to call helper"); var helper = new XMLHelper(r); this.log("Helper: " + helper); var obj = helper.toObject(); } else { obj = r; } this.log("OBJ: " + obj); return obj; }, usdCheckForLoginError: function(obj){ this.log("SOAP BODY? " + obj['soapenv:Body']); this.log("SOAP Fault? " + obj['soapenv:Body']['soapenv:Fault']); this.log("SOAP Fault Detail? " + obj['soapenv:Body']['soapenv:Fault'].detail); if( obj['soapenv:Body'] && obj['soapenv:Body']['soapenv:Fault'] && obj['soapenv:Body']['soapenv:Fault']){ var fault = obj['soapenv:Body']['soapenv:Fault'].detail; this.log("FAULT ERROR CODE: " + fault.ErrorCode); if(fault.ErrorCode == "1010"){ return true; } } return false; }, log:function(msg, level){ if(!level){ level=3 }; if( level <= this.logLevel ){ gs.log(msg, this.logSource); } }, type: 'CASDHelper' } |
The Library in Action – Scripting with the library
Here are some coding examples using the library above:
1 2 3 4 5 6 7 8 9 | gs.log("I started the query!"); var a = new CASDHelper(); //Query with Select Command - library will automatically authenticate if necessary var ticket = a.usdDoSelect("cr", "ref_num = '"+current.u_handle+"'", 1000, ["description","affected_resource", "persistent_id"]); gs.log("Description: " + ticket.Attributes.Attribute[0].AttrValue); gs.log("Affected Resource: " + ticket.Attributes.Attribute[1].AttrValue); gs.log("Persistent ID: " + ticket.Attributes.Attribute[2].AttrValue); |
This is fantastic piece of information, what I understood here is that this blog talks about how to about a particular record on CA service Desk side from ServiceNow.
Do you have something some material where you can discuss two way integration. For example say a ticket gets created in CA Service Desk for a particular group same can be created in ServiceNow and vice versa in additional to handling ticket updates on either sides.
Hello Vinay,
Unfortunately, my experience was only limited to pushing and pulling data to/from CA with the originating request coming from ServiceNow. I believe CA has the capability of sending a SOAP request, but I couldn’t tell you how at this point as I am not experienced in it.
Hello John,
I am going to try and use the updateObject function that is specified in the CA Service Desk WSDL. Do you have an example of this function’s usage by chance?
I am thinking of creating a new function in the Helper script include and that I will need to use the usdDoSelect function in order to get the handle to the CA Service desk record I would like to update. Also do you know if the name value pair list parameters that the updateObject SOAP function takes are comma separated lists?
Greetings,
Eloy Bayona
Hello Eloy. Unfortunately, I don’t have an example of that, nor did I work with it at all. If you do get it working, please feel free to share with the rest of us! 🙂
Hello John,
I would like to share the progress that I have made on the integration.
1) I changed some of the CASD Helper script include functions in order to make them Asynchronous.
2) Created a scheduled job which runs every 15 minutes and calls the usdLogin function synchronously in order to get the session ID token and save this token in a system property. This way the token is always fresh and the other functions in the CASD Helper script include just read the token off the system property avoiding the synchronous call to get the session ID.
3) I inserted the WSDL xml file contents into the SOAP Message record in Service Now and used the “Generate SOAP Message Functions” link. After this I had to place some text in the “SOAP Action” column of each function generated since each was left blank by the “Generate SOAP Message Functions” link and this was causing a “no SOAPAction header!” error when the functions were being called from the CASD Helper script include functions.
4) In the CASD Helper functions which pass parameters using the SOAPMessage object I replaced the “s.setXMLParameter(‘attributes’, attribs);” line with something like: “s.setParameter(‘attrVals’, attrValues);”. Also I did not wait for a response here and just called s.post();
5) I created a before Business Rule on the ECC Queue with a condition like:
current.source.match(‘USD_R11_WebService$’) == ‘USD_R11_WebService’ && current.queue == “input” && current.state == “ready” && current.topic == “SOAPProbe” && (current.name == ‘CASD_createRequest’ || current.name == ‘CASD_updateObject’ )
here the names are whatever you put as the SOAP Action entry for the SOAP functions being used.
This Business rule is the one that handles the XML response and extracts the information required to update a ticket in Service Now.
This is a very high level explanation of the portion which allows me to send SOAP messages to CA and process the responses asynchronously. I use this code in order to sync particular tickets with CA so there is more code that takes care of the scenarios that have been identified. This code is not included in the high level explanation.
Hope this makes sense and adds to the information already posted here.
Greetings,
Eloy Bayona
@Eloy – Great stuff…I love to hear “the rest of the story” with people that take these snippets of solutions and make them something really useful!
Hi John,
I will try to come up with a more detailed description that includes the rewritten functions. Hopefully in the not so far future.
Greetings,
Eloy Bayona