russ_sarbora
ServiceNow Employee
ServiceNow Employee

In my last post, I showed how Runbook will automatically convert a valid XML payload into a Javascript object that you can use in your sensor script. Its a nice feature, but the post was a bit abstract. Next, I think we should poke into some more concrete examples. We'll start with my current favorite activity - Run Powershell

As I said in the last post, Powershell is very good at manipulating XML. You can turn pretty much any Powershell object into an XMLDocument using the ConvertTo-XML cmdlet. Then the OuterXML property will serialize that into an xml string for you. If you're at a Powershell prompt, try something like:



(Get-WmiObject Win32_ComputerSystem | ConvertTo-Xml).OuterXml

You should see a very large chunk of xml, and you're probably thinking, "Cool, I'll just thow that into my Run Powershell activity and I'm golden". But there's a couple problems:
1. The generated xml contains an xml declaration.
2. The default output formatting used by the Run Powershell activity escapes the < and > characters into entities.

Both of these issues confuse the XML parser when the activity payload comes back in. Since the parser doesn't recognize the payload as XML, the payload will just be plopped into document.result.output as a string. If your sensor script is trying to dot-walk the result, you'll see an error like "The undefined value has no properties" in your Runbook's workflow log.

If you'd like to make a working example and follow along, here's the steps:
1. open the Workflow editor and create a new Workflow
2. drag a Run Powershell activity onto the transition line between Begin and End
3. put in the IP of any Windows machine as the Hostname (or you can use 127.0.0.1 to make the MID server the target)
4. cut and paste the above PS code into the script block on your activity
5. cut and paste the following into the sensor script on your activity


gs.log(document.result.output);


Execute the runbook and take a look at the System Log (adding a filter on contains "workflow" or contains "script" will make it much easier to find the entries you care about). You'll see that our log statement spit out the very large xml chunk. It looks pretty, but its not so useful. Now change the sensor script to:


gs.log(JSUtil.describeObject(document.result.output));

Execute the runbook again. This time, the log will say "Log Object null, undefined, or not an object". That's because our xml was not recognized as xml and consequently wasn't converted into an object, its just a string.

Luckily there's an easy workaround for this problem.
1. You can avoid the xml declaration by serializing the root element of the document instead of the document itself.
2. Adding an explicit Write-Host of the results will prevent the entity escaping.
3. Finally, we can reduce the xml size considerably by eliminating all the type information that's added by default. Put all those together, and the PS script changes to:


Write-Host (Get-WmiObject Win32_ComputerSystem | ConvertTo-Xml -NoTypeInformation).DocumentElement.OuterXml


Now execute the runbook again. You should see something like:


Objects:
Object:
Property: Array of 74 elements
[0]: Object
#text: string = 2
@Name: string = __GENUS
[1]: Object
#text: string = Win32_ComputerSystem
@Name: string = __CLASS



This time, our xml was recognized, and converted into a Javascript object. The describeObject call is recursively walking and printing out the name and values of properties on that object.
By the way, if you're scripting in ServiceNow, and you aren't familiar with JSUtil.describeObject, stop what you're doing right now, and get yourself schooled up on it. Its one of the most useful debugging tools we have for scripters. Seriously, do not pass Go, do not collect $200, go directly to the JSUtil script include, read through the describeObject code and then go play with it a little. It should only take a couple minutes and will save you hours of frustration while debugging your scripts.